// xConq
// Handling of Generic GDL Images

// $Id: imf.cc,v 1.1 2006/04/07 02:38:29 eric_mcdonald Exp $

/*//////////////////////////// COPYRIGHT NOTICE ////////////////////////////////

  Copyright (C) 1994-2001   Stanley T. Shebs
  Copyright (C) 2005-2006   Eric A. McDonald

//////////////////////////////////// LICENSE ///////////////////////////////////

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

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

/*! \file
    \brief Handling of generic GDL images.
    \ingroup grp_media
*/

#include "media/imf.h"
 
/* RGB above this value should be considered white. */

#define WHITE_THRESHOLD (65535 - 256)

/* RGB below this value should be considered black. */

#define BLACK_THRESHOLD (0 + 255)

NAMESPACE_XCONQ_BEGIN
NAMESPACE_MEDIA_BEGIN

enum {
    K_MONO_,
    K_MASK_,
    K_COLR_,
    K_FILE_,
    K_OTHER_
};

typedef struct _MFEntry {
    int value, count;
} MFEntry;
typedef MFEntry *ModeFilter;

static Image *get_subimg(ImageFamily *imf, int w, int h);
static Image *largest_image(ImageFamily *imf);
static int image_pixel_at(Image *img, int imtype, int x, int y);
static void set_image_pixel_at(Image *img, int imtype, int x, int y, int val);
static ImageFamily *new_imf(char *name);
static int bitmaps_match(int w, int h, Obj *lispdata, char *rawdata);
static int color_matches_mono(Image *img);
static void write_pixmap(FILE *fp, int w, int h, int aw, int ah,
			 int pixelsize, int orig_pixelsize,
			 Obj *palette, int *rawpalette, int numcolors,
			 Obj *lispdata, char *rawdata);
static void write_bitmap(FILE *fp, char *subtyp, int w, int h,
			 Obj *data, char *rawdata);
static void write_palette_contents(FILE *fp, Obj *palette,
				   int *rawpalette, int numcolors);
static void write_color(FILE *fp, int n, int r, int g, int b);
static ModeFilter new_mf(int maxvals);
static void add_to_mf(ModeFilter mf, int val);
static int mode_of_mf(ModeFilter mf);
static void zero_mf(ModeFilter mf);
static void delete_mf(ModeFilter mf);
static void add_hex_mask(Image *img);
static void remove_hex_mask(Image *img);
static void scale_image_layer(Image *imgin, Image *imgout, int layer,
    int use_mask, ModeFilter mf);
static void scale_image(ImageFamily *imf, Image *img, Image *img2, int dhm);
static Image *add_scaled_image(ImageFamily *imf, Image *img, int w, int h);
static int size_match_score(int wa, int ha, int wb, int hb);
static int calculate_hch(int h);

/* This is the array and count of known image families. */

ImageFamily **images;

int numimages = 0;

/* Head of the linked list of image files. */

ImageFile *image_files;

/* Head of the linked list of "file" images used by several image families. */

FileImage *file_images;

ImageFamily *(*imf_load_hook)(ImageFamily *imf);

ImageFamily *(*imf_interp_hook)(ImageFamily *imf, Image *img, int force);

short write_synthetic_also;

/* There are two ways to use image families.  The "old" way is to use
   the image families' masks and other bitmaps as clip masks.  tcl/tk
   for Windows doesn't support clip masks however, so the "new" way is
   to do a GXand with the mask, carving out a mask-shaped black hole,
   then do a GXor with the image proper.  Note that the part of the
   image that is not included in the mask must be converted to black
   if the ORing is to have the right effect.  X doesn't guarantee that
   ANDing and ORing will work in general though; we just know that the
   X emulation in tcl/tk for Windows can be used in this way. */

/* This should be TRUE everywhere except on Windows. */

int use_clip_mask = TRUE;

/* Flag that indicates limited GDI memory in Windows ME and below. */

short poor_memory = FALSE;

/* Create and return an image family. */

static ImageFamily *
new_imf(char *name)
{
    ImageFamily *imf;

    imf = (ImageFamily *) xmalloc(sizeof(ImageFamily));
    imf->name = name;
    imf->notes = lispnil;
    return imf;
}

ImageFamily *
clone_imf(ImageFamily *imf)
{
    Image *img, *img2, *truenext;
    ImageFamily *imf2;

    imf2 = new_imf(imf->name);
    memcpy(imf2, imf, sizeof(ImageFamily));
    /* Clear the hook, we expect that the caller of this routine will
       supply any new hook that might be necessary. */
    imf2->hook = NULL;
    imf2->images = NULL;
    imf2->numsizes = 0;
    /* Clone the images. */
    for_all_images(imf, img) {
	img2 = get_img(imf2, img->w, img->h);
	truenext = img2->next;
	memcpy(img2, img, sizeof(Image));
	/* Clear the hook, we expect that the caller of this routine
	   will supply any new hook that might be necessary. */
	img2->hook = NULL;
	/* Restore the link. */
	img2->next = truenext;
	/* Note that pointers to raw image data and suchlike can be
	   left as-is, since they should be shared by image clones. */
	/* (should copy anyway, for safety?) */
    }
    return imf2;
}

/* Test that the given name is a valid image family name (all alphanumeric,
   hyphens anywhere but as first char). */

int
valid_imf_name(char *name)
{
    char *tmp;

    for (tmp = name; *tmp; ++tmp) {
	if (!(isalnum(*tmp)
	      || (tmp != name && *tmp == '-')))
	  return FALSE;
    }
    return TRUE;
}

/* Bash invalid chars in a prospective imf name. */

void
validify_imf_name(char *buf)
{
    char *tmp;

    for (tmp = buf; *tmp; ++tmp) {
	if (tmp == buf && *tmp == '-')
	  *tmp = 'Z';
	if (!isalnum(*tmp) && *tmp != '-')
	  *tmp = '-';
    }
}

/* Given a name, find or create an image family with that name. */

ImageFamily *
get_imf(char *name)
{
    ImageFamily *imf = NULL;
    
    if (name == NULL) {
	init_warning("can't get an unnamed image family");
	return NULL;
    }
    if (!valid_imf_name(name)) {
	init_warning("\"%s\" is not a valid image family name", name);
	return NULL;
    }
    if (images == NULL) {
	images =
	  (ImageFamily **) xmalloc(MAXIMAGEFAMILIES * sizeof(ImageFamily *));
    }
    imf = find_imf(name);
    if (imf == NULL) {
	if (numimages >= MAXIMAGEFAMILIES) {
	    init_warning("MAXIMAGEFAMILIES exceeded, skipping image family %s", name);
	    return NULL;
	}
	imf = new_imf(copy_string(name));
	if (imf != NULL) {
	    images[numimages++] = imf;
	}
    }
    return imf;
}

ImageFile *
get_image_file(char *name)
{
    ImageFile *imfile;
    
    if (name == NULL)
      run_error("can't get an unnamed image file");
    for (imfile = image_files; imfile != NULL; imfile = imfile->next) {
	if (strcmp(name, imfile->name) == 0)
	  return imfile;
    }
    imfile = (ImageFile *) xmalloc(sizeof(ImageFile));
    imfile->name = copy_string(name);
    imfile->next = image_files;
    image_files = imfile;
    return imfile;
}

void
load_image_families(FILE *fp, int loadnow,
		    void (*callback)(ImageFamily *imf, int loadnow))
{
    int done = FALSE, first = TRUE;
    char buf[BUFSIZE], *buf1, *buf2, *tmp;
    ImageFamily *imf = NULL;
    ImageFile *imfile;
    
    while (!done) {
	/* Get a line from the file and parse it. */
	if (fgets(buf, BUFSIZE-1, fp)) {
	  buf1 = buf;
	  buf2 = strchr(buf, ' ');
	  if (buf2 == NULL)
	    break;
	  *buf2 = '\0';
	  ++buf2;
	  tmp = strchr(buf2, '\n');
	  if (tmp)
	    *tmp = '\0';
	} else
	  break;
	if (strcmp(buf1, ".") == 0
	    && strcmp(buf2, ".") == 0)
	  done = TRUE;
	else if (first) {
	    if (strcmp(buf1, "ImageFamilyName") == 0
		&& strcmp(buf2, "FileName") == 0)
	      first = FALSE;
	    else {
		init_warning("File not a valid imf dir, will close and ignore");
		/* We've already given a warning message, so pretend we're done
		   so the format error message doesn't get displayed below. */
		done = TRUE;
		break;
	    }
	} else {
	    imf = get_imf(buf1);
	    if (imf != NULL) {
		imfile = get_image_file(buf2);
		imf->location = imfile;
		if (loadnow && !imfile->loaded) {
		    load_imf_file(imfile->name, callback);
		    imfile->loaded = TRUE;
		} else {
		    if (callback != NULL)
		      (*callback)(imf, loadnow);
		}
	    }
	}
    }
    if (!done) {
	init_warning("Format error in imf dir near %s, will only use part",
		     (imf ? imf->name : "???"));
    }
}

/* Given a filename, open it and read/interpret all the image-related
   forms therein. */

int
load_imf_file(char *filename, void (*callback)(ImageFamily *imf, int loadnow))
{
    int startlineno = 1, endlineno = 1;
    Obj *form;
    FILE *fp;

    fp = open_file(filename, "r");
    if (fp != NULL) {
	/* Read everything in the file. */
	while ((form = read_form(fp, &startlineno, &endlineno)) != lispeof) {
	    interp_imf_form(form, filename, callback);
	}
	fclose(fp);
	return TRUE;
    }
    return FALSE;
}

/* Interpret a form, looking specifically for image-related forms. */

void
interp_imf_form(Obj *form, char *filename, void (*imf_callback)(ImageFamily *imf, int loadnow))
{
    Obj *head;
    ImageFamily *imf;
    ImageFile *imfile;

    /* Ignore any non-lists, we might be reading from a normal game design. */
    if (!consp(form))
      return;
    head = car(form);
    if (match_keyword(head, K_IMF)) {
	imf = interp_imf(form);
	if (imf != NULL) {
		imfile = get_image_file(filename);
		imf->location = imfile;
		imf->location->loaded = TRUE;
		if (imf_callback != NULL) {
			(*imf_callback)(imf, TRUE);
		}
	}
    } else {
	/* Ignore any non-image forms, we might be reading from a 
	   normal game design. */
    }
}

/* Find the image family of the given name, if it exists. */

ImageFamily *
find_imf(char *name)
{
    int i;

    for (i = 0; i < numimages; ++i) {
	if (strcmp(name, images[i]->name) == 0)
	  return images[i];
    }
    return NULL;
}

/* Get an image of the given size from the family, creating a new one
   if necessary. */

Image *
get_img(ImageFamily *imf, int w, int h)
{
    Image *img, *nimg, *previmg;

    for_all_images(imf, img) {
	if (w == img->w && h == img->h)
	  return img;
    }
    /* Not found; create a new image and add it to the family. */
    nimg = (Image *) xmalloc(sizeof(Image));
    nimg->w = w;  nimg->h = h;
    nimg->embedx = nimg->embedy = -1;
    nimg->embedw = nimg->embedh = -1;
    nimg->monodata = nimg->colrdata = nimg->maskdata = lispnil;
    nimg->filedata = lispnil;
    nimg->palette = lispnil;
    nimg->actualw = w;  nimg->actualh = h;
    nimg->notes = lispnil;
    nimg->bboxw = w;  nimg->bboxh = h;
    /* Rely on zeroing of xmalloc blocks to avoid clearing other fields. */
    /* Link in order by size, smallest first. */
    previmg = NULL;
    for_all_images(imf, img) {
	if ((nimg->w < img->w)
	    || (nimg->w == img->w && nimg->h < img->h))
	  break;
	previmg = img;
    }
    if (previmg != NULL) {
	nimg->next = previmg->next;
	previmg->next = nimg;
    } else {
	nimg->next = imf->images;
	imf->images = nimg;
    }
    ++(imf->numsizes);
    return nimg;
}

/* Get an image of the given size from the family, creating a new one
   if necessary. */

Image *
get_subimg(ImageFamily *imf, int w, int h)
{
    Image *nimg;

    /* Not found; create a new image and add it to the family. */
    nimg = (Image *) xmalloc(sizeof(Image));
    nimg->w = w;  nimg->h = h;
    nimg->embedx = nimg->embedy = -1;
    nimg->embedw = nimg->embedh = -1;
    nimg->monodata = nimg->colrdata = nimg->maskdata = lispnil;
    nimg->filedata = lispnil;
    nimg->palette = lispnil;
    nimg->actualw = w;  nimg->actualh = h;
    nimg->notes = lispnil;
    nimg->bboxw = w;  nimg->bboxh = h;
    return nimg;
}

Image *
find_img(ImageFamily *imf, int w, int h)
{
    Image *img;
	
    for_all_images(imf, img) {
	if (w == img->w && h == img->h)
	  return img;
    }
    return NULL;
}

ImageFamily *
interp_imf(Obj *form)
{
    ImageFamily *imf;

    if (stringp(cadr(form))) {
	imf = get_imf(c_string(cadr(form)));
	if (imf != NULL) {
	    interp_imf_contents(imf, cddr(form));
	}
	return imf;
    } else {
	run_warning("image family name must be a string");
    }
    return NULL;
}

/* Interpret the image family definition as a list of images and/or a
   notes property. */

void
interp_imf_contents(ImageFamily *imf, Obj *clauses)
{
    Obj *rest, *clause;

    for_all_list(clauses, rest) {
	clause = car(rest);
	if (consp(clause)) {
	    if (symbolp(car(clause))) {
		if (match_keyword(car(clause), K_NOTES)) {
		    imf->notes = cadr(clause);
		    syntax_error(clause, "extra junk after property value");
		} else {
		    syntax_error(clause, "unknown image family property");
		}
	    } else if (consp(car(clause))) {
		interp_image(imf, car(clause), cdr(clause));
	    } else {
		syntax_error(clause, "not image or image family property");
	    }
	} else {
	    syntax_error(clause, "bogus clause");
	}
    }
    compute_image_bboxes(imf);
}

/* Given an image family, a size, and a list describing the elements
   of a single image, parse the size and elements, put those into the
   right slots of an image object.  Also detect and warn about changes
   to an existing image, since this usually indicates some kind of
   problem. */

void
interp_image(ImageFamily *imf, Obj *size, Obj *parts)
{
    int w, h, imtype, emx, emy, emw, emh, numsubs, subi;
    char *name;
    Image *img, *subimg;
    Obj *head, *rest, *typ, *prop, *proptype, *datalist;
    
    w = c_number(car(size));  h = c_number(cadr(size));
    img = get_img(imf, w, h);
    if (img == NULL)
      run_error("no image?");
    if (img->w == 1 && img->h == 1) {
	/* A color is more like a tile than an icon. */
	img->istile = TRUE;
	img->palette = cons(cons(new_number(0), parts), lispnil);
	return;
    }
    if (match_keyword(car(cddr(size)), K_TILE))
      img->istile = TRUE;
    if (match_keyword(car(cddr(size)), K_TERRAIN))
      img->isterrain = TRUE;
    if (match_keyword(car(cddr(size)), K_CONNECTION))
      img->isconnection = TRUE;
    if (match_keyword(car(cddr(size)), K_BORDER))
      img->isborder = TRUE;
    if (match_keyword(car(cddr(size)), K_TRANSITION))
      img->istransition = TRUE;
    numsubs = 0;
    for_all_list(parts, rest) {
	head = car(rest);
	typ = car(head);
	imtype = K_OTHER_;
	if (match_keyword(typ, K_MONO)) {
	    imtype = K_MONO_;
	} else if (match_keyword(typ, K_MASK)) {
	    imtype = K_MASK_;
	} else if (match_keyword(typ, K_COLOR)) {
	    imtype = K_COLR_;
	} else if (match_keyword(typ, K_FILE)) {
	    imtype = K_FILE_;
	} else if (match_keyword(typ, K_EMBED)) {
	    name = c_string(cadr(head));
	    if (img->embedname != NULL
		&& strcmp(img->embedname, name) != 0)
	      run_warning("Changing embed name from \"%s\" to \"%s\" in %dx%d image of \"%s\"",
			  img->embedname, name, w, h, imf->name);
	    img->embedname = name;
	} else if (match_keyword(typ, K_EMBED_AT)) {
	    emx = c_number(cadr(head));  emy = c_number(caddr(head));
	    if ((img->embedx >= 0 && emx != img->embedx)
		|| (img->embedy >= 0 && emy != img->embedy))
	      run_warning("Changing embed x,y from %d,%d to %d,%d in %dx%d image of \"%s\"",
			  img->embedx, img->embedy, emx, emy, w, h, imf->name);
	    img->embedx = emx;  img->embedy = emy;
	} else if (match_keyword(typ, K_EMBED_SIZE)) {
	    emw = c_number(cadr(head));  emh = c_number(caddr(head));
	    if ((img->embedw >= 0 && emw != img->embedw)
		|| (img->embedh >= 0 && emh != img->embedh))
	      run_warning("Changing embed w,h from %d,%d to %d,%d in %dx%d image of \"%s\"",
			  img->embedw, img->embedh, emw, emh, w, h, imf->name);
	    img->embedw = emw;  img->embedh = emh;
	} else if (match_keyword(typ, K_HEXGRID)) {
	    img->hexgridx = c_number(cadr(head));
	    img->hexgridy = c_number(caddr(head));
	    numsubs = img->hexgridx*img->hexgridy;
	} else if (match_keyword(typ, K_NOTES)) {
	    img->notes = cadr(head);
	    syntax_error(head, "extra junk after image notes property");
	} else if (match_keyword(typ, K_X)) {
	    numsubs = c_number(cadr(head));
	    if (cddr(head) != lispnil) {
		img->subx = c_number(caddr(head));
		img->suby = c_number(car(cdddr(head)));
	    }
	} else {
	    run_warning("unknown image property in \"%s\"", imf->name);
	}
	/* If there is no actual image data to process, skip to the next
	   clause in the form. */
	if (imtype == K_OTHER_)
	  continue;
	datalist = cdr(head);
	/* Interpret random image subproperties. */
	while (consp(car(datalist))) {
	    prop = car(datalist);
	    proptype = car(prop);
	    if (match_keyword(proptype, K_ACTUAL)) {
		img->actualw = c_number(cadr(prop));
		img->actualh = c_number(caddr(prop));
	    } else if (match_keyword(proptype, K_PIXEL_SIZE)) {
		img->pixelsize = c_number(cadr(prop));
	    } else if (match_keyword(proptype, K_PALETTE)) {
		img->palette = cdr(prop);
	    } else {
		char imferrbuf[200];

		sprintlisp(imferrbuf, prop, 100);
		run_warning("unknown image subproperty in \"%s\": %s",
			    imf->name, imferrbuf);
	    }
	    datalist = cdr(datalist);
	}
	switch (imtype) {
	  case K_MONO_:
	    if (img->monodata != lispnil && !equal(datalist, img->monodata))
	      run_warning("Changing mono data in %dx%d image of \"%s\"",
			  w, h, imf->name);
	    img->monodata = datalist;
	    break;
	  case K_COLR_:
	    if (img->colrdata != lispnil && !equal(datalist, img->colrdata))
	      run_warning("Changing color data in %dx%d image of \"%s\"",
			  w, h, imf->name);
	    img->colrdata = datalist;
	    break;
	  case K_MASK_:
	    if (img->maskdata != lispnil && !equal(datalist, img->maskdata))
	      run_warning("Changing mask data in %dx%d image of \"%s\"",
			  w, h, imf->name);
	    img->maskdata = datalist;
	    break;
	  case K_FILE_:
	    if (img->filedata != lispnil && !equal(datalist, img->filedata))
	      run_warning("Changing file data in %dx%d image of \"%s\"",
			  w, h, imf->name);
	    img->filedata = datalist;
	    break;
	  default:
	    break;
	}
    }
    /* Allocate space for any subimages that might be needed. */
    /* First set some standard numbers of subimages. */
    if (img->isborder) {
	numsubs = 16;
    } else if (img->isconnection) {
	numsubs = 64;
    } else if (img->istransition) {
	numsubs = 4 * 4;
    /* Limit the number of terrain subimages if we lack memory. */
    } else if (poor_memory) { /* FIXME - what about hexgrid? */
    	numsubs = min(numsubs, 3);
    }
    /* Deal with possible weird situations. */
    if (img->numsubimages > 0 && numsubs != img->numsubimages) {
	run_warning("Going from %d to %d subimages in %dx%d image of \"%s\"",
		    img->numsubimages, numsubs, w, h, imf->name);
	img->subimages = NULL;
    }
    img->numsubimages = numsubs;
    if (img->subimages == NULL) {
	img->subimages = (Image **) xmalloc(numsubs * sizeof(Image *));
	for (subi = 0; subi < numsubs; ++subi) {
	    subimg = get_subimg(imf, img->w, img->h);
	    img->subimages[subi] = subimg;
	}
    }
}

void
compute_image_bboxes(ImageFamily *imf)
{
    Image *img;

    if (imf == NULL)
      return;
    for_all_images(imf, img) {
	compute_image_bbox(img);
    }
}

void
compute_image_bbox(Image *img)
{
    int numbytes, i, j = 0, byte, x, y, x1, x2, k;
    int xmin, ymin, xmax, ymax;
    char *data = NULL;
    Obj *datalist, *next;

    datalist = img->maskdata;
    numbytes = img->h * computed_rowbytes(img->w, 1);
    x = y = 0;
    xmin = img->w;  ymin = img->h;
    xmax = 0;  ymax = 0;
    for (i = 0; i < numbytes; ++i) {
	if (img->maskdata != lispnil) {
	    if (data == NULL || data[j] == '\0') {
		next = car(datalist);
		if (!stringp(next)) {
		    syntax_error(datalist, "garbage in image data list");
		    return;
		}
		data = c_string(next);
		j = 0;
		datalist = cdr(datalist);
	    }
	    /* Just skip over slashes, which are for readability only. */
	    if (data[j] == '/')
	      ++j;
	    byte = hextoi(data[j]) * 16 + hextoi(data[j+1]);
	    j += 2;
	} else if (img->rawmaskdata != NULL) {
	    byte = img->rawmaskdata[i] & 0xff;
	} else {
	    byte = 0xff;
	}
	if (byte != 0) {
	    /* Find the most-significant and least-significant bits in
	       the mask byte. */
	    x1 = x2 = -1;
	    k = 0;
	    while (byte != 0) {
		if ((byte & 0x1) != 0 && x2 < 0)
		  x2 = x + 7 - k;
		byte >>= 1;
		if (byte == 0 && x1 < 0)
		  x1 = x + 7 - k;
		++k;
	    }
	    xmin = min(x1, xmin);  ymin = min(y, ymin);
	    xmax = max(x2, xmax);  ymax = max(y, ymax);
	}
	x += 8;
	if (x >= img->w) {
	    x = 0;
	    ++y;
	}
    }
    /* Compute position and size of bounding box. */
    if (xmin <= xmax && ymin <= ymax) {
	img->bboxx = xmin;  img->bboxy = ymin;
	img->bboxw = xmax - xmin;  img->bboxh = ymax - ymin;
    }
}

/* Get a single pixel from the given type of data of an image. */

int
image_pixel_at(Image *img, int imtype, int x, int y)
{
    int rowbytes, psize, i, byte, rslt;
    char *rawdata = NULL;

    if (imtype == K_MONO_) {
	rawdata = img->rawmonodata;
	psize = 1;
    } else if (imtype == K_MASK_) {
	rawdata = img->rawmaskdata;
	psize = 1;
    } else if (imtype == K_COLR_) {
	rawdata = img->rawcolrdata;
	psize = img->pixelsize;
    }
    if (rawdata == NULL)
      return 0;
    rowbytes = computed_rowbytes(img->w, psize);
    i = y * rowbytes + ((x * psize) >> 3);
    byte = rawdata[i];
    rslt = (byte >> ((8 - psize) - ((x * psize) & 0x7))) & ((1 << psize) - 1);
    return rslt;
}

/* Set a single pixel in the given type of data of an image. */

void
set_image_pixel_at(Image *img, int imtype, int x, int y, int val)
{
    int rowbytes, psize, i, byte;
    char *rawdata = NULL;

    if (imtype == K_MONO_) {
	rawdata = img->rawmonodata;
	psize = 1;
    } else if (imtype == K_MASK_) {
	rawdata = img->rawmaskdata;
	psize = 1;
    } else if (imtype == K_COLR_) {
	rawdata = img->rawcolrdata;
	psize = img->pixelsize;
    }
    if (rawdata != NULL) {
	rowbytes = computed_rowbytes(img->w, psize);
	i = y * rowbytes + ((x * psize) >> 3);
	byte = rawdata[i];
        byte &= ~ (((1 << psize) - 1) << ((8 - psize) - ((x * psize) & 0x7)));
	byte |= val << ((8 - psize) - ((x * psize) & 0x7));
	rawdata[i] = byte;
    }
}

/* Create a new data structure for mode filtering; maxvals is how many
   distinct values we might call add_to_mf on.  The way this works is that
   you create the structure with new_mf, then you call add_to_mf a bunch of
   times with different values.  Then you can call mode_of_mf and it'll
   tell which value you used the most times in calls to add_to_mf.  Calling
   zero_mf resets the filter in a way that is cheaper than deleting and
   re-creating it, and delete_mf is called when you're finally finished. */

static ModeFilter
new_mf(int maxval)
{
    ModeFilter rval = (ModeFilter)xmalloc(sizeof(MFEntry)*(maxval+1));
   
    return rval;
}

/* Add a new value to the mode filter. */

static void
add_to_mf(ModeFilter mf, int val)
{
    int i;
    MFEntry mfe;
   
    if (mf == NULL) return;
    for (i = 0; (mf[i].value != val) && (mf[i].count != 0); i++);
    mf[i].value = val;
    mf[i].count++;
    if (mf[i].count > mf[0].count) {
        mfe = mf[i];
        mf[i] = mf[0];
        mf[0] = mfe;
    }
}

/* Find the most common value in the mode filter. */

static int
mode_of_mf(ModeFilter mf)
{
    if (mf != NULL)
        return mf[0].value;
    else
        return 0;
}

/* Empty the mode filter of all counts. */

static void
zero_mf(ModeFilter mf)
{
    int i;
   
    if (mf == NULL) return;
    for (i = 0; mf[i].count != 0; i++)
        mf[i].count = 0;
    mf[0].value = 0;
}

/* Get rid of the mode filter. */

static void
delete_mf(ModeFilter mf)
{
    if (mf != NULL) free(mf);
}

/* Calculate hch by a formula that matches Stan's magic tables, but also
   gives reasonable results at intermediate sizes.  Needed because the
   standard hex shapes have been tweaked away from geometric perfection,
   for graphics expediency. */

static int
calculate_hch(int h)
{
    return (h*3)/4 + h/72 + h/144 + 1;
}

/* Mask the image down to a hexagon that fills the image rectangle - points
   at the centres of the top and bottom, flat sides along left and right. */

static void
add_hex_mask(Image *img)
{
    int tw, th, x, y, numbytes;
   
    if (img == NULL)
        return;
    if (img->w > img->h)
        return;
  
    /* Ensure that binary version of mask exists.  Create one even if none
       was specified. */
    if (img->rawmaskdata == NULL) {
	img->rawmaskdata = (char *)xmalloc(numbytes);
        if (img->maskdata != lispnil)
	    interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
        else
	    for (y = 0; y < img->h; y++)
	        for (x = 0; x < img->w; x++)
	            set_image_pixel_at(img, K_MASK_, x, y, 1);
    }

    /* Calculate size of triangles to mask out */
    tw = img->w / 2 - 1;
    th = img->h - calculate_hch(img->h) - 1;
   
    /* Mask out the bits */
    for (x = 1; x <= tw; x++)
        for (y = 0; y <= (x*th) / tw; y++) {
	    set_image_pixel_at(img, K_MASK_, tw-x, y, 0);
	    set_image_pixel_at(img, K_MASK_, tw+1+x, y, 0);
	    set_image_pixel_at(img, K_MASK_, tw-x, img->h-1-y, 0);
	    set_image_pixel_at(img, K_MASK_, tw+1+x, img->h-1-y, 0);
	}
}

/* Remove the mask from the four triangular areas outside the inscribed
   hexagon (see add_hex_mask above).  Fill in those triangles with data
   copied from the diagonal edges just inside the hex area.  This is
   useful because it means we can do remove_hex_mask, scale up, and then
   add_hex_mask, and the result will have smooth edges instead of
   magnified jaggies from the lower resolution.  Warning: caller must
   load raw mono and color data before calling this, this function won't
   do that. */
 
static void
remove_hex_mask(Image *img)
{
    int tw, th, x, y, ylim, c1, c2, c3, c4, numbytes;
   
    if (img == NULL)
        return;
    if (img->w > img->h)
        return;
  
    /* Ensure that binary version of mask exists.  Abort if no mask.  */
    if (img->rawmaskdata == NULL) {
	img->rawmaskdata = (char *)xmalloc(numbytes);
        if (img->maskdata != lispnil)
	    interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
        else
	    return;
    }

    /* Calculate size of triangles to unmask */
    tw = img->w / 2 - 1;
    th = img->h - calculate_hch(img->h) - 1;
   
    /* Unmask the pixels */
    for (x = 1; x <= tw; x++) {
        ylim = (x*th) / tw;
        c1 = image_pixel_at(img, K_MASK_, tw-x, ylim+1);
	c2 = image_pixel_at(img, K_MASK_, tw+1+x, ylim+1);
	c3 = image_pixel_at(img, K_MASK_, tw-x, img->h-2-ylim);
	c4 = image_pixel_at(img, K_MASK_, tw+1+x, img->h-2-ylim);
        for (y = 0; y <= ylim; y++) {
	    set_image_pixel_at(img, K_MASK_, tw-x, y, c1);
	    set_image_pixel_at(img, K_MASK_, tw+1+x, y, c2);
	    set_image_pixel_at(img, K_MASK_, tw-x, img->h-1-y, c3);
	    set_image_pixel_at(img, K_MASK_, tw+1+x, img->h-1-y, c4);
	}
        if (img->rawmonodata != NULL) {
	    c1 = image_pixel_at(img, K_MONO_, tw-x, ylim+1);
	    c2 = image_pixel_at(img, K_MONO_, tw+1+x, ylim+1);
	    c3 = image_pixel_at(img, K_MONO_, tw-x, img->h-2-ylim);
	    c4 = image_pixel_at(img, K_MONO_, tw+1+x, img->h-2-ylim);
            for (y = 0; y <= ylim; y++) {
	        set_image_pixel_at(img, K_MONO_, tw-x, y, c1);
	        set_image_pixel_at(img, K_MONO_, tw+1+x, y, c2);
	        set_image_pixel_at(img, K_MONO_, tw-x, img->h-1-y, c3);
	        set_image_pixel_at(img, K_MONO_, tw+1+x, img->h-1-y, c4);
	    }
	}
        if (img->rawcolrdata != NULL) {
	    c1 = image_pixel_at(img, K_COLR_, tw-x, ylim+1);
	    c2 = image_pixel_at(img, K_COLR_, tw+1+x, ylim+1);
	    c3 = image_pixel_at(img, K_COLR_, tw-x, img->h-2-ylim);
	    c4 = image_pixel_at(img, K_COLR_, tw+1+x, img->h-2-ylim);
            for (y = 0; y <= ylim; y++) {
	        set_image_pixel_at(img, K_COLR_, tw-x, y, c1);
	        set_image_pixel_at(img, K_COLR_, tw+1+x, y, c2);
	        set_image_pixel_at(img, K_COLR_, tw-x, img->h-1-y, c3);
	        set_image_pixel_at(img, K_COLR_, tw+1+x, img->h-1-y, c4);
	    }
	}
    }
}

/* Scale one layer of an image. */

static void
scale_image_layer(Image *imgin, Image *imgout, int layer, int use_mask,
		  ModeFilter mf)
{
    int u, v, x, y, xa, xb, ya, yb, tmp;

    /* Perform a mode-filtered scaling operation. */
   
    /* The way this works is that u and v are coordinates in the output image
       rval.  For each pixel (u,v) we compute the rectangle (xa..xb,ya..yb)
       in the input image img, which rectangle covers all the pixels that are
       touched by the pixel (u,v) when it's projected onto img, noting that
       a pixel is defined to be closed on the sides with lesser coordinate
       values and open on the sides with greater coordinate values.  We
       perform a mode filter over (xa..xb,ya..yb), that is, we find the
       pixel value that occurs most commonly in that rectangle, splitting
       ties by preferring the value that occurs first in reading order; the
       result is the pixel value for the pixel (u,v).  We do not count input
       pixels that are masked out.  If the scaling is by less than a factor
       of 2 up or down, the result will be basically the same as
       nearest-neighbour resampling. */
   
    /* Loop for each pixel (u,v) */
    for (v = 0; v < imgout->h; v++) {
      
        /* Might as well calculate ya and yb here because v determines them */
        ya = (v*imgin->h)/imgout->h;
        yb = ((v+1)*imgin->h-1)/imgout->h;
      
        for (u = 0; u < imgout->w; u++) {

	    /* Calculate xa and xb */
	    xa = (u*imgin->w)/imgout->w;
	    xb = ((u+1)*imgin->w-1)/imgout->w;
	  
	    /* If the rectangle is small, and we don't have a mask, then
	       skip the mode filter business because the first pixel wins. */
	    if ((xb-xa+1)*(yb-ya+1) <= 2 && !use_mask) {
	        set_image_pixel_at(imgout, layer, u, v,
		    image_pixel_at(imgin, layer, xa, ya));

	    /* Otherwise we have to do the filter thing. */
	    } else {
	      
	        /* Look through the input rectangle and compute modes */
	        for (y = yb; y >= ya; y--)
		    for (x = xb; x >= xa; x--)
		        if (!use_mask
			    || image_pixel_at(imgin, K_MASK_, x, y) != 0)
		        add_to_mf(mf, image_pixel_at(imgin, layer, x, y));
	      
	        /* Set output pixel */
		set_image_pixel_at(imgout, layer, u, v, mode_of_mf(mf));
		zero_mf(mf);
	    }
        }
    }
}

/* Scale an image (or subimage). */

static void
scale_image(ImageFamily *imf, Image *img, Image *img2, int dhm)
{
    int numbytes, numbytes2;
    ModeFilter mf;

    /* Scale the embedding coordinates. */
    img2->embedx = (img->embedx*img2->w)/img->w;
    img2->embedy = (img->embedy*img2->h)/img->h;
    img2->embedw = (img->embedw*img2->w)/img->w;
    img2->embedh = (img->embedh*img2->h)/img->h;

    /* Actually get the input image if we haven't yet. */
    if (img->rawcolrdata == NULL) {
	/* Try different ways to get some image data. */
	if (img->colrdata != lispnil) {
	    numbytes = img->h * computed_rowbytes(img->w, img->pixelsize);
	    img->rawcolrdata = (char *)xmalloc(numbytes);
	    interp_bytes(img->colrdata, numbytes, img->rawcolrdata, 0);
	} else if (img->filedata != lispnil) {
	    make_image_from_file_image(imf, img, img, 0);
	}
    }

    /* Copy over a bunch of other fields. */
    img2->pixelsize = img->pixelsize;
    img2->palette = img->palette;
    img2->rawpalette = img->rawpalette;
    img2->numcolors = img->numcolors;
    img2->istile = img->istile;
    img2->isterrain = img->isterrain;
    img2->isconnection = img->isconnection;
    img2->isborder = img->isborder;
    img2->istransition = img->istransition;
    img2->hexgridx = img->hexgridx;
    img2->hexgridy = img->hexgridy;

    /* Mark the image as having been computed rather than read in. */
    img2->synthetic = TRUE;

    if (img->rawcolrdata != NULL) {
	numbytes2 = img2->h * computed_rowbytes(img2->w, img2->pixelsize);
	img2->rawcolrdata = (char *)xmalloc(numbytes2);
    }
    numbytes = img->h * computed_rowbytes(img->w, 1);
    numbytes2 = img2->h * computed_rowbytes(img2->w, 1);

    /* Ensure that binary version of mono image exists. */
    make_raw_mono_data(img, FALSE);
    if (img->rawmonodata != NULL)
      img2->rawmonodata = (char *)xmalloc(numbytes2);

    /* Ensure that binary version of mask exists. */
    if (img->rawmaskdata == NULL && img->maskdata != lispnil) {
	img->rawmaskdata = (char *)xmalloc(numbytes);
	interp_bytes(img->maskdata, numbytes, img->rawmaskdata, 0);
    }
    if (img->rawmaskdata != NULL)
      img2->rawmaskdata = (char *)xmalloc(numbytes2);
   
    /* Tweak the input image's mask if appropriate */
    if (dhm)
        remove_hex_mask(img);
   
    /* Do the scaling operation */
    mf = new_mf(256);
    if (img2->rawmaskdata != NULL) {
        if (img2->rawcolrdata != NULL)
	    scale_image_layer(img, img2, K_COLR_, TRUE, mf);
        if (img2->rawmonodata != NULL)
	    scale_image_layer(img, img2, K_MONO_, TRUE, mf);
        scale_image_layer(img, img2, K_MASK_, FALSE, mf);
    } else {
        if (img2->rawcolrdata != NULL)
	    scale_image_layer(img, img2, K_COLR_, FALSE, mf);
        if (img2->rawmonodata != NULL)
	    scale_image_layer(img, img2, K_MONO_, FALSE, mf);
    }
    delete_mf(mf);
   
    /* Put the hex masks back */
    if (dhm) {
        add_hex_mask(img);
        add_hex_mask(img2);
    }

    /* Set the image bounding box */
    compute_image_bbox(img2);
   
    /* Recognize flat colors */
    if (img2->w == 1 && img2->h == 1 && img2->rawcolrdata != NULL) {
	if (img2->rawpalette == NULL)
	    make_raw_palette(img2);
        img2->istile = TRUE;
        img2->r = img2->rawpalette[4 * img2->rawcolrdata[0] + 1];
        img2->g = img2->rawpalette[4 * img2->rawcolrdata[0] + 2];
        img2->b = img2->rawpalette[4 * img2->rawcolrdata[0] + 3];
    }
   
    /* Call the interface hook */
    if (imf_interp_hook)
        (*imf_interp_hook)(imf, img2, FALSE);
}

/* Scale image img in family imf to size w by h and add it to the family. */

static Image *
add_scaled_image(ImageFamily *imf, Image *img, int w, int h)
{
    Image *rval;
    int i, dhm;
   
    /* We must have a family and an input image, and the size must be sane. */
    if (imf == NULL || img == NULL || w <= 0 || h <= 0)
        return NULL;
   
    /* Create a data structure for the output. */
    rval = get_img(imf, w, h);
   
    /* Figure out whether to Do Hex Masking */
    dhm = (img->isterrain || img->isconnection
	|| img->istransition) && (img->h >= img->w) && (h >= w);
   
    /* Scale the main image */
    scale_image(imf, img, rval, dhm);
   
    /* Handle subimages */
    if (img->numsubimages > 0 && img->subimages != NULL) {
        rval->numsubimages = img->numsubimages;
        rval->subimages
	    = (Image **) xmalloc(img->numsubimages * sizeof(Image *));
        for (i = 0; i < img->numsubimages; i++) {
	    rval->subimages[i] = get_subimg(imf, rval->w, rval->h);
	    scale_image(imf, img->subimages[i], rval->subimages[i], dhm);
	}
    }

    /* Return result */
    return rval;
}

void
make_raw_mono_data(Image *img, int force)
{
    int numbytes = img->h * computed_rowbytes(img->w, 1);

    if ((img->rawmonodata == NULL || force) && img->monodata != lispnil) {
	img->rawmonodata = (char *)xmalloc(numbytes);
	interp_bytes(img->monodata, numbytes, img->rawmonodata, 0);
    }
}

/* Given a list of strings, interpret the hex digits and put the
   results at the given address. */

void
interp_bytes(Obj *datalist, int numbytes, char *destaddr, int jump)
{
    int i, j = 0;
    char *data = NULL;

    for (i = 0; i < numbytes; ++i) {
	if (data == NULL || data[j] == '\0') {
	    if (datalist == lispnil) {
		return;
	    } else if (stringp(car(datalist))) {
		data = c_string(car(datalist));
		j = 0;
	    } else {
		syntax_error(datalist, "Non-string in image data list");
		/* Have to give up now. */
		return;
	    }
	    datalist = cdr(datalist);
	}
	/* Just skip over slashes, which are for readability only. */
	if (data[j] == '/')
	  ++j;
	destaddr[i] = hextoi(data[j]) * 16 + hextoi(data[j+1]);
	if (jump == 1 || (jump > 0 && i % jump == 0)) {
	    i += jump;
	    /* Be neat, put a zero in the location we're jumping over. */
	    /* (doesn't work for jump > 1, but that never happens anymore?) */
	    destaddr[i] = 0;
	}
	j += 2;
    }
}

/* Compute a score describing how much scaling or tiling a given image will
   need to match a given size. */

static int
size_match_score(int wa, int ha, int wb, int hb)
{
    if (!wb) wb = 1;  if (!wa) wa = 1;  if (!hb) hb = 1;  if (!ha) ha = 1;
    return (wa*100)/wb + (wb*100)/wa + (ha*100)/hb + (hb*100)/ha - 400;
}

/* Try to find an image within the specified range and as close to the
   specified size as possible in the family.  If no designer-specified
   image is available in the range, generate one of size w by h by
   scaling. */

Image *
best_image_in_range(ImageFamily *imf, int w, int h, int wmin, int hmin,
		    int wmax, int hmax)
{
    Image *img, *best_nonsynth = NULL, *best_in_range = NULL,
        *best_tile = NULL, *exact_match = NULL;
    int best_nonsynth_score = INT_MAX, best_tile_score = INT_MAX, 
	best_in_range_score = INT_MAX, s;

    if (imf == NULL || imf->images == NULL)
        return NULL;

    for_all_images(imf, img) {

        /* Skip all basic terrain images except power 4 and 5
	   if we are low on memory. */
    	if (poor_memory
    	    && img->isterrain
    	    && img->w != 24
    	    && img->w != 44) {
    		continue;
    	}
       
        /* Don't cross the isometric/non-isometric boundary */
        if (img->isterrain || img->isborder || img->isconnection ||
	     img->istransition) {
	    if (img->w > img->h && w <= h)
	        continue;
	    if (img->w <= img->h && w > h)
	        continue;
	}
       
        /* Check if there's an exact match - this is no longer an immediate
	   success because it might be synthetic */
        if (img->w == w && img->h == h && !img->istile)
	    exact_match = img;

        /* Find best image that isn't synthetic or tile (scaling candidate) */
        if (!img->istile && !img->synthetic) {
	    s = size_match_score(w, h, img->w, img->h);
	    if (best_nonsynth == NULL || s < best_nonsynth_score) {
	        best_nonsynth = img;
	        best_nonsynth_score = s;
	    }
	   
	    /* Find best in range */
	    if ((best_in_range == NULL || s < best_in_range_score)
		&& img->w >= wmin && img->h >= hmin
		&& img->w <= wmax && img->h <= hmax) {
	        best_in_range = img;
	        best_in_range_score = s;
	    }
	}

        /* Find best tile */
        if (img->istile) {
	    s = size_match_score(w, h, img->w, img->h);
	    if (best_tile == NULL || s < best_tile_score) {
	        best_tile = img;
	        best_tile_score = s;
	    }
	}
    } /* for_all_images */
   
    /* If we have a "best in range" then use it */
    if (best_in_range != NULL)
        return best_in_range;
   
    /* If we have an exact size match, use that */
    if (exact_match != NULL)
        return exact_match;

    /* If we have a tile, and we're low on memory, we can't scale, or
       the best scaling candidate isn't as good as the tile, use tile. */
    if (best_tile != NULL && (poor_memory || best_nonsynth == NULL ||
			      best_tile_score < best_nonsynth_score))
        return best_tile;
   
    /* Now we know we want to scale, so there had better exist a candidate.
       If not, we still might try looking for a flat colour. */
    if (best_nonsynth == NULL) {
        if ((w != 1) || (h != 1)) {
	    return best_image_in_range(imf, 1, 1, 1, 1, 1, 1);
        } else {
	    return NULL;
	}
    }

    /* Scale the candidate. */
    return add_scaled_image(imf, best_nonsynth, w, h);
}

Image *
smallest_image(ImageFamily *imf)
{
    Image *img, *smallest = NULL;

    if (imf == NULL)
      return NULL;
    for_all_images(imf, img) {
	if (smallest == NULL || (img->w < smallest->w && img->h < smallest->h))
	  smallest = img;
    }
    return smallest;
}

Image *
largest_image(ImageFamily *imf)
{
    Image *img, *largest = NULL;

    if (imf == NULL)
      return NULL;
    for_all_images(imf, img) {
	if (largest == NULL || (img->w > largest->w && img->h > largest->h))
	  largest = img;
    }
    return largest;
}

/* Compute the right location for the given emblem and unit images. */

static int tmpbw;  /* work around Think C bug */

int
emblem_position(Image *uimg, char *ename, ImageFamily *eimf, int sw, int sh,
		int vpuh, int vphh, int *exxp, int *eyyp, int *ewp, int *ehp)
{
    int ew1, eh1, ex, ey, ew, eh, bx, by, bw, bh, overlap;
    Image *eimg;

    /* Check if correct emblem is part of the unit's image, and don't draw 
       if it is. */
    if (uimg
	&& uimg->embedname
	&& ename != NULL
	&& strcmp(uimg->embedname, ename) == 0) {
	return FALSE;
    }
    /* (should use emblem bbox to help calc) */
    /* Get the size of the emblem, either from the image or by computing
       a reasonable default. */
    if (uimg && uimg->embedw > 0 && uimg->embedh > 0) {
	ew = uimg->embedw;  eh = uimg->embedh;
    } else {
	ew1 = min(sw, max(8, sw / 4));  eh1 = min(sh, max(8, sh / 4));
	eimg = NULL;
	/* Look up the best emblem for the current zoom. */
	if (eimf != NULL) {
	    eimg = best_image(eimf, ew1, eh1);
	}
	if (eimg) {
	    ew = eimg->w;  eh = eimg->h;
	    /* Make a default 8x6 size for a solid color emblem. */
	    if (ew == 1 && eh == 1) {
		ew = 8;  eh = 6;
	    }
	} else {
	    ew = ew1;  eh = eh1;
	}
    }
    /* Position the emblem, either explicitly, or default to UR corner
       (note that we need the emblem's width to do this) */
    if (uimg && uimg->embedx >= 0 && uimg->embedy >= 0) {
	ex = uimg->embedx;  ey = uimg->embedy;
	/* Don't let the emblem stick out of the unit's area. */
	if (ex + ew > sw)
	  ex = sw - ew;
	if (ey + eh > sh)
	  ey = sh - eh;
    } else if (uimg && (uimg->bboxw != uimg->w || uimg->bboxh != uimg->h)) {
	overlap = FALSE;
	/* Scale bounding box by space given to image. */
	bx = (uimg->bboxx * sw) / uimg->w;  by = (uimg->bboxy * sh) / uimg->h;
	tmpbw = (uimg->bboxw * sw) / uimg->w;
	bh = (uimg->bboxh * sh) / uimg->h;
	bw = tmpbw;
	/* Position the emblem outside the image's bbox if possible,
	   moving in if necessary to stay inside the image's allowed
	   area (sw x sh). */
	ex = bx + bw;
	if (ex + ew > sw) {
	    /* Emblem too wide to fit between unit bbox and edge of
	       area; butt it against edge of area, note the
	       overlap. */
	    ex = sw - ew;
	    overlap = TRUE;
	}
	if (ex < 0)
	  ex = 0;
	ey = by;
	if (overlap)
	  ey -= eh;
	if (ey + eh > sh)
	  ey = sh - eh;
	if (ey < 0)
	  ey = 0;
    } else {
	ex = sw - ew;  ey = 0;
    }
    /* Adjust for an oversized unit image. */
    if (sh > vpuh) {
	ey += (vphh - vpuh) / 2;
#if (0)
	/* Tweaked by hand. */
	if (vpuh > 16) {
	    ex -= 1;
	}
#endif
    }
    /* Adjust for a shrunken unit image. */
    if (sh < vpuh) {
	ex = sw - ew;
	/* Tweaked by hand. */
	if (vpuh > 8) {
	    ey = vpuh / 8;
	}
    }
    /* Return the results. */
    *exxp = ex;  *eyyp = ey;
    *ewp = ew;  *ehp = eh;
    return TRUE;
}

/* Transform the masked-out part of an image into a single chosen
   color. */

void
blacken_masked_area(ImageFamily *imf, Image *img, int rd, int g, int b)
{
    int r, ri, rc, c, rmask;
    int rmi, rmc, rmmask;
    char *rp, *rmp;
    int black = -1;
    int rowsz = -1;

    if (img->rawpalette == NULL)
      make_raw_palette(img);
    for (c = 0; c < img->numcolors; c++) {
	if (img->rawpalette[4 * c + 1] == rd
	    && img->rawpalette[4 * c + 2] == g
	    && img->rawpalette[4 * c + 3] == b) {
	    black = c;
	    break;
	}
    }
    if (black < 0
	&& (1 << img->pixelsize) == img->numcolors
	&& img->pixelsize < 8) {
	char *newdata, *nrp;
	int newpsize, nrmask, nri;

	newpsize = img->pixelsize * 2;
	/* Don't need to mess with palette because it already has an
	   additional spot allocated, and we only need the one. */
	rmask = (1 << img->pixelsize) - 1;
	ri = 8 - img->pixelsize;
	rp = img->rawcolrdata;
	newdata = (char *)xmalloc(img->h * computed_rowbytes(img->w, newpsize));
	nrmask = (1 << newpsize) - 1;
	nri = 8 - newpsize;
	nrp = newdata;
	for (r = 0; r < img->h; r++) {
	  for (c = 0; c < img->w; c++) {
	    rc = ((int) (*rp >> ri)) & rmask;
	    /* OR the color data into its new location. */
	    *nrp |= (char) (rc << nri);
	    if (ri) {
		ri -= img->pixelsize;
	    } else {
		ri = 8 - img->pixelsize;
		++rp;
	    }
	    if (nri) {
		nri -= newpsize;
	    } else {
		nri = 8 - newpsize;
		++nrp;
	    }
	  }
	  if ((img->pixelsize * img->w) % 8) {
	    ri = 8 - img->pixelsize;
	    ++rp;
	  }
	  if ((newpsize * img->w) % 8) {
	    nri = 8 - newpsize;
	    ++nrp;
	  }
	}
	img->orig_pixelsize = img->pixelsize;
	img->pixelsize = newpsize;
	img->rawcolrdata = newdata;
    }
    /* If a spare color is available, put black there. */
    if (black < 0 && (1 << img->pixelsize) > img->numcolors) {
	black = img->numcolors;
	img->rawpalette[4 * black + 0] = black;
	img->rawpalette[4 * black + 1] = rd;
	img->rawpalette[4 * black + 2] = g;
	img->rawpalette[4 * black + 3] = b;
	++(img->numcolors);
    }
    rmask = (1 << img->pixelsize) - 1;
    ri = 8 - img->pixelsize;
    rp = img->rawcolrdata;
    /* OK, now we're getting desperate; use the color in the upper left
       corner and pretend it's a transparent color. But, only if it 
       appears to be the vertex of two edges of the same color. */
    /*! \note This is a stupid hack. In cases where the UL corner cannot 
	      be relied upon, the designer should have the option of 
	      specifying a 'mask-color' keyword with a RGB triplet. */
    if (black < 0) {
	black = ((int) *rp >> ri) & rmask;
	rowsz = computed_rowbytes(img->w, img->pixelsize);
	/* Check pixels in UR and LL corners. */
	/* If the corners don't match, then grab something from the LR 
	   corner and hope for the best. (This is very hackish!) */
	if ((black != (((int) rp[rowsz - 1] >> ri) & rmask))
	    || (black != (((int) rp[rowsz * (img->h - 2)] >> ri) & rmask))) {
	    black = ((int) rp[(rowsz * img->h) - 1] >> ri) & rmask;
	}
	img->rawpalette[4 * black + 1] = rd;
	img->rawpalette[4 * black + 2] = g;
	img->rawpalette[4 * black + 3] = b;
    }
    rmmask = 1;
    rmi = 7;
    rmp = img->rawmaskdata;
    for (r = 0; r < img->h; r++) {
	for (c = 0; c < img->w; c++) {
	    rc = ((int) (*rp >> ri)) & rmask;
	    rmc = ((int) (*rmp >> rmi)) & rmmask;
	    if (rmc == 0) {
		/* Mask off the old value. */
		*rp &= (char) (~ (rmask << ri));
		/* Insert the color for black. */
		*rp |= (char) (black << ri);
	    }
	    if (ri) {
		ri -= img->pixelsize;
	    } else {
		ri = 8 - img->pixelsize;
		++rp;
	    }
	    if (rmi) {
		rmi -= 1;
	    } else {
		rmi = 7;
		++rmp;
	    }
	}
	if ((img->pixelsize * img->w) % 8) {
	    ri = 8 - img->pixelsize;
	    ++rp;
	}
	if (img->w % 8) {
	    rmi = 7;
	    ++rmp;
	}
    }
}

void
blacken_mono_masked_area(ImageFamily *imf, Image *img, int rd, int g, int b)
{
    int r, ri, rc, c, rmask;
    int rmi, rmc, rmmask;
    char *rp, *rmp;

    rmask = 1;
    ri = 7;
    rp = img->rawmonodata;
    rmmask = 1;
    rmi = 7;
    rmp = img->rawmaskdata;
    for (r = 0; r < img->h; r++) {
	for (c = 0; c < img->w; c++) {
	    rc = ((int) (*rp >> ri)) & rmask;
	    rmc = ((int) (*rmp >> rmi)) & rmmask;
	    if (rmc == 0) {
		/* Mask off the old value. */
		*rp &= (char) (~ (rmask << ri));
		/* Insert the color for black. */
		*rp |= (char) (1 << ri);
	    }
	    if (ri) {
		ri -= 1;
	    } else {
		ri = 7;
		++rp;
	    }
	    if (rmi) {
		rmi -= 1;
	    } else {
		rmi = 7;
		++rmp;
	    }
	}
	if (img->w % 8) {
	    ri = 7;
	    ++rp;
	}
	if (img->w % 8) {
	    rmi = 7;
	    ++rmp;
	}
    }
}

void
make_raw_palette(Image *img)
{
    int ipal[4][256];
    int c, ln;
    Obj *pal;

    /* Parse the Lispified palette. */
    /* (should allocate and store directly instead of using ipal) */
    c = 0;
    for_all_list(img->palette, pal) {
	parse_lisp_palette_entry(car(pal), &ipal[0][c],
				 &ipal[1][c], &ipal[2][c], &ipal[3][c]);
	c++;
    }
    img->numcolors = c;
    if (c == 0)
      return;
    /* store palette */
    img->rawpalette = (int *) xmalloc(257/*(img->numcolors + 1)*/ * 4 * sizeof(int));
    for (c = 0; c < img->numcolors; c++) {
	for (ln = 0; ln < 4; ln++) {
	    img->rawpalette[4 * c + ln] = ipal[ln][c];
	}
    }
}

/* The comparison function for the image list just does "strcmp" order
   and *requires* that all image families be named and named uniquely. */

static int
image_name_compare(const void *imf1, const void *imf2)
{
    return strcmp((*((ImageFamily **) imf1))->name,
		  (*((ImageFamily **) imf2))->name);
}

void
sort_all_images(void)
{
    qsort(&(images[0]), numimages, sizeof(ImageFamily *), image_name_compare);
}

/* Check Lisp-format and binary-format data for consistency. */

void
check_imf(ImageFamily *imf)
{
    Image *img;
    
    if (imf == NULL)
      return;
    if (imf->name == NULL) {
	return;
    }
    for_all_images(imf, img) {
	/* Check consistency of Lisp and binary data. */
	if (img->colrdata != lispnil && img->rawcolrdata) {
	    /* (should add color image comparison) */
	}
	if (img->monodata != lispnil && img->rawmonodata) {
	    if (!bitmaps_match(img->w, img->h, img->monodata, img->rawmonodata))
	      run_warning("mono bitmap data not consistent in  %dx%d image of \"%s\"",
			  img->w, img->h, imf->name);
	}
	if (img->maskdata != lispnil && img->rawmaskdata) {
	    if (!bitmaps_match(img->w, img->h, img->maskdata, img->rawmaskdata))
	      run_warning("mask bitmap data not consistent in  %dx%d image of \"%s\"",
			  img->w, img->h, imf->name);
	}
    }
}

static int
bitmaps_match(int w, int h, Obj *lispdata, char *rawdata)
{
    int i, j = 0, rowbytes, numbytes, byte;
    char *datastr = NULL;

    rowbytes = computed_rowbytes(w, 1);
    numbytes =  h * rowbytes;
    for (i = 0; i < numbytes; ++i) {
	if (datastr == NULL || datastr[j] == '\0') {
		if (!stringp(car(lispdata)))
		  break;
		datastr = c_string(car(lispdata));
		j = 0;
		lispdata = cdr(lispdata);
	}
	if (datastr[j] == '/')
	  ++j;
	byte = hextoi(datastr[j]) * 16 + hextoi(datastr[j+1]);
	j += 2;
	if (byte != rawdata[i])
	  return FALSE;
    }
    return TRUE;
}

/* Write the imf directory for the given set of images. */

void
write_imf_dir(char *filename, ImageFamily **imfimages, int num)
{
    int i;
    char *loc, *token, *delims;
    ImageFamily *imf;
    FILE *fp;

    fp = open_file(filename, "w");
    if (fp != NULL) {
	fprintf(fp, "ImageFamilyName FileName\n");
	for (i = 0; i < num; ++i) {
	    imf = images[i];
	    loc = "???";
	    if (imf->location && !empty_string(imf->location->name)) {
		/* First remove any Unix, Mac or Windows pathnames. */
		loc = copy_string(imf->location->name);
		delims = "/:\\";
		token = strtok(loc, delims);
		while (token != NULL) {
			loc = token;
			token = strtok(NULL, delims);
		}
		/* Remove any leading dots left from Unix pathnames. */
		loc += strspn(loc, ".");
	    }
	    fprintf(fp, "%s %s\n", imf->name, loc);
	    /* (to write imf files, should scan through images once for
	       each file, writing all images found that are in that file) */
	}
	fprintf(fp, ". .\n");
	fclose(fp);
    } else {
	run_warning("could not open file \"%s\" for writing", filename);
    }
}

/* Write out the entire image family. */

void
write_imf(FILE *fp, ImageFamily *imf)
{
    Obj *palent, *posdata;
    Image *img;
    
    if (fp == NULL || imf == NULL)
      return;
    if (imf->name == NULL) {
	fprintf(fp, "; garbage image family?\n");
	return;
    }
    if (imf->notes != lispnil) {
	fprintf(fp, "(%s \"%s\"", keyword_name(K_IMF), imf->name);
	fprintf(fp, "\n  (%s ", keyword_name(K_NOTES));
	fprintlisp(fp, imf->notes);
	fprintf(fp, "))\n");
    }
    for_all_images(imf, img) {
	if (img->monodata != lispnil
	    || img->maskdata != lispnil
	    || img->colrdata != lispnil
	    || img->filedata != lispnil
	    || img->rawmonodata != NULL
	    || img->rawmaskdata != NULL
	    || img->rawcolrdata != NULL
	    || (img->w == 1 && img->h == 1)) {
	    /* Skip over synthesized images. */
	    if (img->synthetic && !write_synthetic_also) {
		continue;
	    }
	    /* Skip over empty (undefined) 1 x 1 images. */
	    if (img->w == 1 
	        && img->h == 1
	        && img->rawpalette == NULL
	        && img->palette == lispnil) {
	    	continue;
	    }
	    fprintf(fp, "(%s \"%s\"", keyword_name(K_IMF), imf->name);
	    fprintf(fp, " (");
	    fprintf(fp, "(%d %d", img->w, img->h);
	    if (img->istile && !(img->w == 1 && img->h == 1))
	      fprintf(fp, " %s", keyword_name(K_TILE));
	    if (img->isterrain)
	      fprintf(fp, " %s", keyword_name(K_TERRAIN));
	    if (img->isconnection)
	      fprintf(fp, " %s", keyword_name(K_CONNECTION));
	    if (img->isborder)
	      fprintf(fp, " %s", keyword_name(K_BORDER));
	    if (img->istransition)
	      fprintf(fp, " %s", keyword_name(K_TRANSITION));
	    fprintf(fp, ")");
	    if (img->numsubimages > 0) {
		fprintf(fp, " (%s %d", keyword_name(K_X), img->numsubimages);
		if (img->subx > 0 || img->suby > 0)
		  fprintf(fp, " %d %d", img->subx, img->suby);
		fprintf(fp, ")");
	    }
	    if (img->hexgridx > 0 && img->hexgridy > 0) {
	        fprintf(fp, " (%s %d %d)", keyword_name(K_HEXGRID),
		    img->hexgridx, img->hexgridy);
	    }
	    if (img->embedname) {
		fprintf(fp, " (%s \"%s\")",
			keyword_name(K_EMBED), img->embedname);
	    }
	    if (img->embedx >= 0 && img->embedy >= 0) {
		fprintf(fp, " (%s %d %d)",
			keyword_name(K_EMBED_AT), img->embedx, img->embedy);
	    }
	    if (img->embedw >= 0 && img->embedh >= 0) {
		fprintf(fp, " (%s %d %d)",
			keyword_name(K_EMBED_SIZE), img->embedw, img->embedh);
	    }
	    if (img->notes != lispnil) {
		fprintf(fp, "\n  (%s ", keyword_name(K_NOTES));
		fprintlisp(fp, img->notes);
		fprintf(fp, ")\n ");
	    }
	    /* Write a single color if that's what this image is. */
	    if (img->w == 1 && img->h == 1) {
		if (img->rawpalette != NULL) {
		    write_color(fp, -1,
				img->rawpalette[1],
				img->rawpalette[2],
				img->rawpalette[3]);
		} else if (img->palette != lispnil) {
		    palent = cdr(car(img->palette));
		    if (stringp(car(palent)) || symbolp(car(palent))) {
			fprintf(fp, " %s", c_string(car(palent)));
		    } else {
			write_color(fp, -1,
				    c_number(car(palent)),
				    c_number(cadr(palent)),
				    c_number(caddr(palent)));
		    }
		}
	    } else if (img->filedata != lispnil) {
		fprintf(fp, " (%s ", keyword_name(K_FILE));
		fprintf(fp, " \"%s\"", c_string(car(img->filedata)));
		posdata = cdr(img->filedata);
		if (posdata != lispnil) {
		    if (symbolp(car(posdata))) {
			fprintf(fp, " %s", c_string(car(posdata)));
			posdata = cdr(posdata);
		    }
		    fprintf(fp, " %d %d",
			    c_number(car(posdata)), c_number(cadr(posdata)));
		}
		fprintf(fp, ")");
	    } else if ((img->colrdata != lispnil || img->rawcolrdata)
			&& !color_matches_mono(img)) {
		fprintf(fp, "\n  ");
		write_pixmap(fp, img->w, img->h, img->actualw, img->actualh,
			     img->pixelsize, img->orig_pixelsize,
			     img->palette, img->rawpalette, img->numcolors,
			     img->colrdata, img->rawcolrdata);
	    }
	    if (img->monodata != lispnil || img->rawmonodata) {
		fprintf(fp, "\n  ");
		write_bitmap(fp, keyword_name(K_MONO), img->w, img->h,
			     img->monodata, img->rawmonodata);
	    }
	    if (img->maskdata != lispnil || img->rawmaskdata) {
		fprintf(fp, "\n  ");
		write_bitmap(fp, keyword_name(K_MASK), img->w, img->h,
			     img->maskdata, img->rawmaskdata);
	    }
	    fprintf(fp, "))\n");
	}
    }
}

/* Study an ostensibly color image to see if its color table includes
   black and white only (white first, then black), and if its data is
   the same as the mono version of the image. */

static int
color_matches_mono(Image *img)
{
    int i, cj, mj, rowbytes, numbytes, cbyte, mbyte;
    int col[2], red[2], grn[2], blu[2];
    char *cdatastr = NULL, *mdatastr = NULL;
    Obj *clispdata = img->colrdata, *mlispdata = img->monodata, *palette;

    if (img->pixelsize != 1)
      return FALSE;

    /* No match possible if not a black-white-only palette. */
    if (img->numcolors > 2)
      return FALSE;

    if (img->rawpalette != NULL) {
	for (i = 0; i < 2; i++) {
	    col[i] = img->rawpalette[4*i+0];
	    red[i] = img->rawpalette[4*i+1];
	    grn[i] = img->rawpalette[4*i+2];
	    blu[i] = img->rawpalette[4*i+3];
	}
    } else if (img->palette != lispnil) {
	palette = img->palette;
	parse_lisp_palette_entry(car(palette), &col[0],
				 &red[0], &grn[0], &blu[0]);
	if (cdr(palette) == lispnil) {
	    /* If only one color in the palette, say the other one is
	       black. */
	    col[1] = 1;
	    red[1] = grn[1] = blu[1] = 0;
	    /* If the one color is black, say it's white. */
	    if (col[0] == 0
		&& red[0] < BLACK_THRESHOLD
		&& grn[0] < BLACK_THRESHOLD
		&& blu[0] < BLACK_THRESHOLD) {
		col[0] = 0;
		red[0] = grn[0] = blu[0] = 65535;
	    }
	} else {
	    /* Parse the second entry in the palette. */
	    parse_lisp_palette_entry(cadr(palette), &col[1],
				     &red[1], &grn[1], &blu[1]);
	}
    } else {
	return FALSE;
    }

    if (!(col[0] == 0
	  && red[0] > WHITE_THRESHOLD
	  && grn[0] > WHITE_THRESHOLD
	  && blu[0] > WHITE_THRESHOLD
	  && col[1] == 1
	  && red[1] < BLACK_THRESHOLD
	  && grn[1] < BLACK_THRESHOLD
	  && blu[1] < BLACK_THRESHOLD))
	return FALSE;

    /* Now compare the contents. */
    rowbytes = computed_rowbytes(img->w, 1);
    numbytes =  img->h * rowbytes;
    cj = mj = 0;
    for (i = 0; i < numbytes; ++i) {
	/* Extract one byte of the color image. */
	if (clispdata != lispnil) {
	    if (cdatastr == NULL || cdatastr[cj] == '\0') {
		if (!stringp(car(clispdata)))
		  break;
		cdatastr = c_string(car(clispdata));
		cj = 0;
		clispdata = cdr(clispdata);
	    }
	    if (cdatastr[cj] == '/')
	      ++cj;
	    cbyte = hextoi(cdatastr[cj]) * 16 + hextoi(cdatastr[cj+1]);
	    cj += 2;
	} else if (img->rawcolrdata != NULL) {
	    cbyte = (img->rawcolrdata)[i];
	} else {
	    return FALSE;
	}
	/* Extract one byte of the mono image. */
	if (mlispdata != lispnil) {
	    if (mdatastr == NULL || mdatastr[mj] == '\0') {
		if (!stringp(car(mlispdata)))
		  break;
		mdatastr = c_string(car(mlispdata));
		mj = 0;
		mlispdata = cdr(mlispdata);
	    }
	    if (mdatastr[mj] == '/')
	      ++mj;
	    mbyte = hextoi(mdatastr[mj]) * 16 + hextoi(mdatastr[mj+1]);
	    mj += 2;
	} else if (img->rawmonodata != NULL) {
	    mbyte = (img->rawmonodata)[i];
	} else {
	    return FALSE;
	}
	/* Compare the bytes. */
	if (cbyte != mbyte)
	  return FALSE;
    }
    return TRUE;
}

static void
write_pixmap(FILE *fp, int w, int h, int actualw, int actualh,
	     int pixelsize, int orig_pixelsize,
	     Obj *palette, int *rawpalette, int numcolors,
	     Obj *lispdata, char *rawdata)
{
    int dolisp, i, j = 0, rowbytes, numbytes, byte;
    char *datastr = NULL;

    actualw = (actualw != 0 ? actualw : w);
    actualh = (actualh != 0 ? actualh : h);
    dolisp = (lispdata != lispnil);
    /* If the pixel size was mangled (Windows) we need to use the original pixel size. */
    rowbytes = computed_rowbytes(actualw, (orig_pixelsize ? orig_pixelsize : pixelsize));
    numbytes = actualh * rowbytes;
    fprintf(fp, "(%s", keyword_name(K_COLOR));
    if (actualw != w || actualh != h)
      fprintf(fp, " (%s %d %d)", keyword_name(K_ACTUAL), actualw, actualh);
    /* (should not use orig_pixelsize if !dolisp?) Yes! See above. */
    fprintf(fp, " (%s %d)", keyword_name(K_PIXEL_SIZE),
	    (orig_pixelsize ? orig_pixelsize : pixelsize));
    if (palette != lispnil || (rawpalette && numcolors))
      write_palette_contents(fp, palette, rawpalette, numcolors);
    fprintf(fp, "\n   \"");
    for (i = 0; i < numbytes; ++i) {
	if (i > 0 && i % 32 == 0)
	  fprintf(fp, "\"\n   \"");
	if (i > 0 && i % 32 != 0 && i % rowbytes == 0)
	  fprintf(fp, "/");
	if (dolisp) {
	    if (datastr == NULL || datastr[j] == '\0') {
		if (!stringp(car(lispdata)))
		  break;
		datastr = c_string(car(lispdata));
		j = 0;
		lispdata = cdr(lispdata);
	    }
	    if (datastr[j] == '/')
	      ++j;
	    byte = hextoi(datastr[j]) * 16 + hextoi(datastr[j+1]);
	    j += 2;
	} else {
	    byte = rawdata[i];
	}
	fprintf(fp, "%02x", (unsigned char) byte);
    }
    fprintf(fp, "\")");
}

static void
write_bitmap(FILE *fp, char *subtyp, int w, int h, Obj *lispdata,
	     char *rawdata)
{
    int dolisp, i, j = 0, rowbytes, numbytes, byte;
    char *datastr = NULL;

    /* Lisp data overrides raw byte data. */	
    dolisp = (lispdata != lispnil);	
    rowbytes = computed_rowbytes(w, 1);
    numbytes =  h * rowbytes;
    fprintf(fp, "(%s", subtyp);
    if (w > 16 || h > 16)
      fprintf(fp, "\n  ");
    fprintf(fp, " \"");
    for (i = 0; i < numbytes; ++i) {
	if (i > 0 && i % 32 == 0)
	  fprintf(fp, "\"\n   \"");
	if (i > 0 && i % 32 != 0 && i % rowbytes == 0)
	  fprintf(fp, "/");
	if (dolisp) {
	    if (datastr == NULL || datastr[j] == '\0') {
		if (!stringp(car(lispdata)))
		  break;
		datastr = c_string(car(lispdata));
		j = 0;
		lispdata = cdr(lispdata);
	    }
	    /* Ignore any slashes, they're for human readability. */
	    if (datastr[j] == '/')
	      ++j;
	    byte = hextoi(datastr[j]) * 16 + hextoi(datastr[j+1]);
	    j += 2;
	} else {
	    byte = rawdata[i];
	}
	fprintf(fp, "%02x", (unsigned char) byte);
    }
    fprintf(fp, "\")");
}

static void
write_palette_contents(FILE *fp, Obj *palette, int *rawpalette, int numcolors)
{
    int len, i, col, red, grn, blu;
    Obj *restpal;

    len = (palette != lispnil ? length(palette) : numcolors);
    if (len > 2)
      fprintf(fp, "\n  ");
    fprintf(fp, " (%s", keyword_name(K_PALETTE));
    if (palette != lispnil) {
	for_all_list(palette, restpal) {
	    parse_lisp_palette_entry(car(restpal), &col, &red, &grn, &blu);
	    if (len > 2)
	      fprintf(fp, "\n   ");
 	    write_color(fp, col, red, grn, blu);
	}
    } else if (rawpalette != NULL) {
	for (i = 0; i < numcolors; i++) {
	    if (len > 2)
	      fprintf(fp, "\n   ");
	    write_color(fp, rawpalette[4*i],
			rawpalette[4*i+1], rawpalette[4*i+2], rawpalette[4*i+3]);
	}
    } else {
	fprintf(fp, " #| no palette? |# ");
    }
    fprintf(fp, ")");
}

static void
write_color(FILE *fp, int n, int r, int g, int b)
{
    char *colorname;

    if (n >= 0)
      fprintf(fp, " (%d", n);
    colorname = find_color_name(r, g, b);
    if (!empty_string(colorname)) {
	/* Write color name.  Note that we write as a symbol, so that
	   each instance of "white" doesn't become a separate string. */
	fprintf(fp, " %s", colorname);
    } else {
	/* Write individual color components. */
	fprintf(fp, " %d %d %d", r, g, b);
    }
    if (n >= 0)
      fprintf(fp, ")");
}

/* Given rgb components, return names of standard colors if the match
   is close. */

char *
find_color_name(int r, int g, int b)
{
    if (r > WHITE_THRESHOLD
	&& g > WHITE_THRESHOLD
	&& b > WHITE_THRESHOLD)
      return "white";
    else if (r < BLACK_THRESHOLD
	&& g < BLACK_THRESHOLD
	&& b < BLACK_THRESHOLD)
      return "black";
    else if (r > WHITE_THRESHOLD
	&& g < BLACK_THRESHOLD
	&& b < BLACK_THRESHOLD)
      return "red";
    else if (r < BLACK_THRESHOLD
	&& g > WHITE_THRESHOLD
	&& b < BLACK_THRESHOLD)
      return "green";
    else if (r < BLACK_THRESHOLD
	&& g < BLACK_THRESHOLD
	&& b > WHITE_THRESHOLD)
      return "blue";
    else
      return NULL;
}

void
parse_lisp_palette_entry(Obj *palentry, int *col, int *red, int *grn, int *blu)
{
    Obj *colorcomp;
    char *colorname;

    *col = c_number(car(palentry));
    colorcomp = cdr(palentry);
    if (colorcomp == lispnil)
        return;
    if (symbolp(car(colorcomp)) || stringp(car(colorcomp))) {
	colorname = c_string(car(colorcomp));
	*red = *grn = *blu = 0;
	if (strcmp(colorname, "white") == 0) {
	    *red = *grn = *blu = 65535;
	} else if (strcmp(colorname, "black") == 0) {
	    /* done */
	} else if (strcmp(colorname, "red") == 0) {
	    *red = 65535;
	} else if (strcmp(colorname, "green") == 0) {
	    *grn = 65535;
	} else if (strcmp(colorname, "blue") == 0) {
	    *blu = 65535;
	} else {
	    init_warning("No color named \"%s\" found, substituting gray",
			 colorname);
	    *red = *grn = *blu = 128 * 256;
	}
    } else if (numberp(car(colorcomp))) {
	*red = c_number(car(colorcomp));
	*grn = c_number(cadr(colorcomp));
	*blu = c_number(caddr(colorcomp));
	/* Assume small values are 8-bit rather than 16-bit colors.
	   (Works because as 16-bit colors, they would all be nearly
	   identical shades of black.) */
	if (*red < 256)
	  *red *= 256;
	if (*grn < 256)
	  *grn *= 256;
	if (*blu < 256)
	  *blu *= 256;
    } else {
	init_warning("palette color info is not a name or set of numbers, ignoring");
    }
}

/* Given a filename, find or create a file image structure for it. */

FileImage *get_file_image(char *fname)
{
    FileImage *fimg, *newfimg;

    for (fimg = file_images; fimg != NULL; fimg = fimg->next) {
	if (strcmp(fimg->name, fname) == 0)
	  return fimg;
    }
    newfimg = (FileImage *) xmalloc(sizeof(FileImage));
    newfimg->name = fname;
    newfimg->next = file_images;
    file_images = newfimg;
    return newfimg;
}

/* Collect the file image for the given image and use it to generate
   the image's (or subimage's) raw data. */

void
make_image_from_file_image(ImageFamily *imf, Image *img, Image *subimg,
			   int subi)
{
    int hch, pad = 2, stdlayout = FALSE;
    int xoffset, yoffset, xoff, yoff;
    Obj *posdata;
    FileImage *fimg;

    if (img->filedata != lispnil) {
	if (img->file_image == NULL) {
	    img->file_image = get_file_image(c_string(car(img->filedata)));
	}
    }
    fimg = img->file_image;
    load_file_image(fimg);
    if (!fimg->loaded) {
	init_warning("Could not load file \"%s\" for image family \"%s\", ignoring",
		     fimg->name, imf->name);
	return;
    }
    xoffset = yoffset = 0;
    stdlayout = FALSE;
    posdata = cdr(img->filedata);
    if (posdata != lispnil) {
	if (match_keyword(car(posdata), K_STD)) {
	    stdlayout = TRUE;
	    posdata = cdr(posdata);
	}
	xoffset = c_number(car(posdata));
	yoffset = c_number(cadr(posdata));
	if (stdlayout) {
	    xoffset = (xoffset * (img->w + 2)) + 2;
	    yoffset = (yoffset * (img->h + 2)) + 2;
	}
    }
    /* All subimages share color data. */
    if (subimg == img ||
	(img->subimages != NULL && subimg == img->subimages[0])) {
	subimg->pixelsize = 8;
	/* Copy the palette over verbatim from the file image. */
	subimg->numcolors = fimg->numcolors;
	subimg->rawpalette =
	  (int *) xmalloc(257 /*subimg->numcolors*/ * 4 * sizeof(int));
	memcpy(subimg->rawpalette, fimg->palette,
	       subimg->numcolors * 4 * sizeof(int));
    } else {
	/* Inherit color data from the first subimage. */
	subimg->pixelsize = img->subimages[0]->pixelsize;
	subimg->numcolors = img->subimages[0]->numcolors;
	subimg->rawpalette = img->subimages[0]->rawpalette;
    }
    if (img->isborder) {
	hch = calculate_hch(img->h);
	xoff = xoffset + (subi % 4) * img->w;
	yoff = yoffset + (subi / 4) * hch;
	copy_from_file_image(subimg, fimg, xoff, yoff, 0, hch);
    } else if (img->isconnection) {
	xoff = xoffset + (subi % 8) * (img->w + pad);
	yoff = yoffset + (subi / 8) * (img->h + pad);
	copy_from_file_image(subimg, fimg, xoff, yoff, 0, 0);
    } else if (img->istransition) {
	xoff = xoffset + (subi % 4) * (img->w + pad);
	yoff = yoffset + (subi / 4) * (img->h + pad);
	copy_from_file_image(subimg, fimg, xoff, yoff, 0, 0);
    } else if (img->numsubimages > 0) {
	/* Figure where the next subimage is at. */
        if (img->hexgridx > 0 && img->hexgridy > 0) {
	    xoff = xoffset + (subi%img->hexgridx) * img->w;
	    if ((subi/img->hexgridx)%2 != 0)
	        xoff += (img->w/2);
	    yoff = yoffset + (subi/img->hexgridx) * ((img->h*3)/4 + 1);
	} else if (img->subx != 0 || img->suby != 0) {
	    xoff = xoffset + subi * img->subx;
	    yoff = yoffset + subi * img->suby;
	} else {
	    xoff = xoffset + subi * img->w;
	    yoff = yoffset;
	}
	copy_from_file_image(subimg, fimg, xoff, yoff, 0, 0);
    } else {
	copy_from_file_image(img, fimg, xoffset, yoffset, 0, 0);
    }
   
    /* If this is any sort of terrain, apply a hex mask, and blow away
       the LISP version of the mask if it no longer matches */
    if (img->isterrain || img->isconnection ||
	img->istransition) {
        add_hex_mask(subimg);
        if (subimg->maskdata != lispnil && !bitmaps_match(subimg->w, subimg->h,
	    subimg->maskdata, subimg->rawmaskdata))
	    subimg->maskdata = lispnil;
    }
}

/* Extract a single color image from a file image.  Also create mask
   data for the image, if the file image has any transparent colors.  */

void
copy_from_file_image(Image *img, FileImage *fimg, int xoffset, int yoffset,
		     int actualw, int actualh)
{
    char pix;
    int i, j, k, ii, val, kk, kkb;

    /* Make space for the color data (assuming 1 byte/pixel). */
    img->rawcolrdata = (char *) xmalloc(img->w * img->h);
    if (fimg->numtransparent > 0)
      img->rawmaskdata =
	(char *) xmalloc(img->h * computed_rowbytes(img->w, 1));
    /* Scan through all the pixels of the subimage we're building. */
    for (i = 0; i < img->h; ++i) {
	for (j = 0; j < img->w; ++j) {
	    k = (yoffset + i) * fimg->width + xoffset + j;
	    pix = fimg->data[k];
	    kk = i * img->w + j;
	    img->rawcolrdata[kk] = pix;
	    if ((actualw > 0 && j >= actualw)
		|| (actualh > 0 && i >= actualh))
	      img->rawcolrdata[kk] = img->rawcolrdata[0];
	    /* If there are transparent colors in the image, modify
	       the mask bitmap. */
	    if (fimg->numtransparent > 0) {
		val = 1;
		for (ii = 0; ii < fimg->numtransparent; ++ii) {
		    if (pix == fimg->transparent[ii]) {
			val = 0;
			break;
		    }
		}
		if (val) {
		    kkb = i * computed_rowbytes(img->w, 1) + j / 8;
		    img->rawmaskdata[kkb] |= 1 << (7 - j % 8);
		    if ((actualw > 0 && j >= actualw)
			|| (actualh > 0 && i >= actualh))
		      img->rawmaskdata[kkb] = 0;
		}
	    }
	}
    }
}

/* Give a file image, attempt to load it into memory. */

void
load_file_image(FileImage *fimg)
{
    int rslt;

    if (fimg->loaded)
      return;
    /* Only doing GIFs right now. */
    rslt = get_gif(fimg);
    if (rslt)
      fimg->loaded = TRUE;
}

/* Generic image setup. */

static short imf_dir_loaded;

ImageFamily *
get_generic_images(char *name)
{
    FILE *fp;
    ImageFamily *imf;
    LibraryPath *path;

    imf = get_imf(name);
    if (imf == NULL)
      return NULL;
    if (imf->numsizes > 0 && imf_interp_hook != NULL)
      imf = (*imf_interp_hook)(imf, NULL, FALSE);
    if (imf_load_hook != NULL)
      imf = (*imf_load_hook)(imf);
    /* Always check that the imf location is loaded 
	(fixes emblem loading from saved games on the mac). */
    if (imf->numsizes == 0 
	|| (imf->location && imf->location->loaded != TRUE)) {
	/* Maybe collect the names/locations of all image families. */
	if (!imf_dir_loaded) {
	    /* (should let name be decided by platform?) */
	    fp = open_library_file("imf.dir");
	    if (fp != NULL) {
		load_image_families(fp, FALSE, NULL);
		fclose(fp);
	    } else {
		init_warning("Cannot open \"%s\", will not use it", "imf.dir");
	    }
	    imf_dir_loaded = TRUE;
	}
	/* Get a (possibly empty) family. */
	imf = get_imf(name);
	if (imf == NULL)
	  return NULL;
	if (imf->location != NULL) {
	    /* Load data filling in the family. */
	    for_all_library_paths(path) {
		make_pathname(path->path, imf->location->name, "", spbuf);
		if (load_imf_file(spbuf, NULL)) {
			imf->location->loaded = TRUE;
			break;
		}
	    }
	    /* Maybe try the plain filename, just in case. */
	    if (!imf->location->loaded) {
	    	imf->location->loaded = 
		    load_imf_file(imf->location->name, NULL);
	    }
	    /* We don't complain here about not finding the file, because
	       we'll get a more useful warning alert later on. */
	    if (imf_interp_hook != NULL)
	      imf = (*imf_interp_hook)(imf, NULL, FALSE);
	}
    }
    return imf;
}

NAMESPACE_MEDIA_END
NAMESPACE_XCONQ_END
