/*
 * xvxpm.c - load routine for X11 XPM v3 format pictures
 *
 * LoadXPM(fname,pinfo)
 * WriteXPM(fp,pic,ptype,w,h,rp,gp,bp,nc,col,name)
 */

/*
 * Written by Chris P. Ross (cross@eng.umd.edu) to support XPM v3
 * format images.
 *
 * Thanks go to Sam Yates (syates@spam.maths.adelaide.edu.au) for
 * providing inspiration.
 */

#define VALUES_LEN	80	/* Max length of values line */
#define TOKEN_LEN	8	/* Max length of color token */

#include "xv.h"


/*
 * File format:
 *  (A file format def should go here)
 *
 */

typedef struct hent {
  char        token[TOKEN_LEN];
  union {
    byte      index;
    byte      rgb[3];
  } color_val;
  struct hent *next;
} hentry;

#define cv_index	color_val.index
#define cv_rgb		color_val.rgb



/* Local (Global) Variables */
static byte	hex[256];
static hentry **hashtab;        /* Hash table */
static int      hash_len;       /* number of hash buckets */
static int	bufchar;	/* Buffered character from XpmGetc */
static short	in_quote;	/* Is the current point in the file in */
                                /*  a quoted string? */

/* Local Functions */
static int     XpmLoadError  PARM((const char *, const char *));
static int     XpmGetc	     PARM((FILE *));
static int     hash          PARM((char *));
static int     hash_init     PARM((int));
static int     hash_insert   PARM((hentry *));
static hentry *hash_search   PARM((char *));
static void    hash_destroy  PARM((void));


/**************************************/
int LoadXPM(char *fname, PICINFO *pinfo)
{
  /* returns '1' on success */

  FILE    *fp;
  hentry   item;
  int      c;
  const char *bname;
  char     values[VALUES_LEN];
  byte    *pic;
  byte    *i_sptr;		/* image search pointer */
  long     filesize;
  int      w, h, nc, cpp, line_pos;
  int      npixels;
  short    i, j, k;		/* for() loop indexes */
  hentry  *clmp;		/* colormap hash-table */
  hentry  *c_sptr;		/* cmap hash-table search pointer*/
  XColor   col;

  bname = BaseName(fname);
  fp = fopen(fname, "r");
  if (!fp)
    return (XpmLoadError(bname, "couldn't open file"));

  if (DEBUG)
    printf("LoadXPM(): Loading xpm from %s\n", fname);

  fseek(fp, 0L, 2);
  filesize = ftell(fp);
  fseek(fp, 0L, 0);

  bufchar = -2;
  in_quote = FALSE;

  /* Read in the values line.  It is the first string in the
   * xpm, and contains four numbers.  w, h, num_colors, and
   * chars_per_pixel. */

  /* First, get to the first string */
  while (((c = XpmGetc(fp))!=EOF) && (c != '"')) ;
  line_pos = 0;

  /* Now, read in the string */
  while (((c = XpmGetc(fp))!=EOF) && (line_pos < VALUES_LEN) && (c != '"')) {
    values[line_pos++] = c;
  }
  if (c != '"')
    return (XpmLoadError(bname, "error parsing values line"));

  values[line_pos] = '\0';
  sscanf(values, "%d%d%d%d", &w, &h, &nc, &cpp);
  if (nc <= 0 || cpp <= 0)
    return (XpmLoadError(bname, "No colours in Xpm?"));

  npixels = w * h;
  if (w <= 0 || h <= 0 || npixels/w != h)
    return (XpmLoadError(bname, "Image dimensions out of range"));

  if (nc > 256)
    pinfo->type = PIC24;
  else
    pinfo->type = PIC8;

  if (DEBUG)
    printf("LoadXPM(): reading a %dx%d image (%d colors)\n", w, h, nc);

  /* We got this far... */
  WaitCursor();

  if (!hash_init(nc))
    return (XpmLoadError(bname, "Not enough memory to hash colormap"));

  clmp = (hentry *) malloc(nc * sizeof(hentry)); /* Holds the colormap */
  if (pinfo->type == PIC8)
    pic = (byte *) malloc((size_t) npixels);
  else {
    int bufsize = 3*npixels;
    if (bufsize/3 != npixels)
      return (XpmLoadError(bname, "Image dimensions out of range"));
    pic = (byte *) malloc((size_t) bufsize);
  }

  if (!clmp || !pic)
    return (XpmLoadError(bname, "Not enough memory to load pixmap"));

  c_sptr = clmp;
  i_sptr = pic;

  /* initialize the 'hex' array for zippy ASCII-hex -> int conversion */

  for (i = 0 ; i < 256 ; i++)   hex[i] = 0;
  for (i = '0'; i <= '9' ; i++) hex[i] = i - '0';
  for (i = 'a'; i <= 'f' ; i++) hex[i] = i - 'a' + 10;
  for (i = 'A'; i <= 'F' ; i++) hex[i] = i - 'A' + 10;

  /* Again, we've made progress. */
  WaitCursor();

  /* Now, we need to read the colormap. */
  pinfo->colType = F_BWDITHER;
  for (i = 0 ; i < nc ; i++) {
    while (((c = XpmGetc(fp))!=EOF) && (c != '"')) ;
    if (c != '"')
      return (XpmLoadError(bname, "Error reading colormap"));

    for (j = 0 ; j < cpp ; j++)
      c_sptr->token[j] = XpmGetc(fp);
    c_sptr->token[j] = '\0';

    while (((c = XpmGetc(fp))!=EOF) && ((c == ' ') || (c == '\t'))) ;
    if (c == EOF)		/* The failure condition of getc() */
      return (XpmLoadError(bname, "Error parsing colormap line"));

    do {
      char  key[3];
      char  color[80];	/* Need to figure a good size for this... */

/*
 *  Problem with spaces in color names
 *
 *    X s Color Name m Other Name c Last Name
 *
 *  ... this parser doesn't find `Any Name'
 */

      for (j=0; j<2 && (c != ' ') && (c != '\t') && (c != EOF); j++) {
	key[j] = c;
	c = XpmGetc(fp);
      }
      key[j] = '\0';

      while (((c = XpmGetc(fp))!=EOF) && ((c == ' ') || (c == '\t'))) ;
      if (c == EOF)	/* The failure condition of getc() */
	return (XpmLoadError(bname, "Error parsing colormap line"));

      for (j=0; j<79 && (c!=' ') && (c!='\t') && (c!='"') && c!=EOF; j++) {
	color[j] = c;
	c = XpmGetc(fp);
      }
      color[j]='\0';

      while ((c == ' ') || (c == '\t'))
	c = XpmGetc(fp);

      if (DEBUG > 1)
	printf("LoadXPM(): Got color key '%s', color '%s'\n",
	       key, color);

      if (key[0] == 's')	/* Don't find a color for a symbolic name */
	continue;

      if (XParseColor(theDisp,theCmap,color,&col)) {
	if (pinfo->type == PIC8) {
	  pinfo->r[i] = col.red >> 8;
	  pinfo->g[i] = col.green >> 8;
	  pinfo->b[i] = col.blue >> 8;
	  c_sptr->cv_index = i;

	  /* Is there a better way to do this? */
	  if (pinfo->colType != F_FULLCOLOR) {
	    if (pinfo->colType == F_GREYSCALE) {
	      if (pinfo->r[i] == pinfo->g[i] &&
		  pinfo->g[i] == pinfo->b[i])
		/* Still greyscale... */
		;
	      else
		/* It's color */
		pinfo->colType = F_FULLCOLOR;
	    } else {
	      if (pinfo->r[i] == pinfo->g[i] &&
		  pinfo->g[i] == pinfo->b[i]) {
		if ((pinfo->r[i] == 0 || pinfo->r[i] == 0xff) &&
		    (pinfo->g[i] == 0 || pinfo->g[i] == 0xff) &&
		    (pinfo->b[i] == 0 || pinfo->b[i] == 0xff))
		  /* It's B/W */
		  ;
		else
		  /* It's greyscale */
		  pinfo->colType = F_GREYSCALE;
	      } else
		/* It's color */
		pinfo->colType = F_FULLCOLOR;
	    }
	  }

	}
	else {   /* PIC24 */
	  c_sptr->cv_rgb[0] = col.red >> 8;
	  c_sptr->cv_rgb[1] = col.green >> 8;
	  c_sptr->cv_rgb[2] = col.blue >> 8;
	}
      }

      else {      /* 'None' or unrecognized color spec */
	int rgb;

	if (strcasecmp(color, "None") == 0) rgb = 0xb2c0dc;  /* infobg */
	else {
	  SetISTR(ISTR_INFO, "%s:  unknown color spec '%s'", bname, color);
	  Timer(1000);
	  rgb = 0x808080;
	}

	if (pinfo->type == PIC8) {
	  pinfo->r[i] = (rgb>>16) & 0xff;
	  pinfo->g[i] = (rgb>> 8) & 0xff;
	  pinfo->b[i] = (rgb>> 0) & 0xff;
	  c_sptr->cv_index = i;
	}
	else {
	  c_sptr->cv_rgb[0] = (rgb>>16) & 0xff;
	  c_sptr->cv_rgb[1] = (rgb>> 8) & 0xff;
	  c_sptr->cv_rgb[2] = (rgb>> 0) & 0xff;
	}
      }


      xvbcopy((char *) c_sptr, (char *) &item, sizeof(item));
      hash_insert(&item);

      if (DEBUG > 1)
	printf("LoadXPM():  Cmap entry %d, 0x%02x 0x%02x 0x%02x, token '%s'\n",
	       i, pinfo->r[i], pinfo->g[i], pinfo->b[i], c_sptr->token);

      if (*key == 'c') {	/* This is the color entry, keep it. */
	while (c!='"' && c!=EOF) c = XpmGetc(fp);
	break;
      }

    } while (c != '"');
    c_sptr++;

    if (!(i%13)) WaitCursor();
  } /* for */


  if (DEBUG)
    printf("LoadXPM(): Read and stored colormap.\n");

  /* Now, read the pixmap. */
  for (i = 0 ; i < h ; i++) {
    while (((c = XpmGetc(fp))!=EOF) && (c != '"')) ;
    if (c != '"')
      return (XpmLoadError(bname, "Error reading colormap"));

    for (j = 0 ; j < w ; j++) {
      char pixel[TOKEN_LEN];
      hentry *mapentry;

      for (k = 0 ; k < cpp ; k++)
	pixel[k] = XpmGetc(fp);
      pixel[k] = '\0';

      if (!(mapentry = (hentry *) hash_search(pixel))) {
	/* No colormap entry found.  What do we do?  Bail for now */
	if (DEBUG)
	  printf("LoadXPM(): Found token '%s', can't find entry in colormap\n",
		 pixel);
	return (XpmLoadError(bname, "Can't map resolve into colormap"));
      }

      if (pinfo->type == PIC8)
	*i_sptr++ = mapentry->cv_index;
      else {
	*i_sptr++ = mapentry->cv_rgb[0];
	*i_sptr++ = mapentry->cv_rgb[1];
	*i_sptr++ = mapentry->cv_rgb[2];
      }
    }  /* for ( j < w ) */
    while (((c = XpmGetc(fp))!=EOF) &&		/* Throw away the close " and */
	(c != '"'));				/* erase all remaining pixels */

    if (!(i%7)) WaitCursor();
  }  /* for ( i < h ) */

  pinfo->pic = pic;
  pinfo->normw = pinfo->w = w;
  pinfo->normh = pinfo->h = h;
  pinfo->frmType = F_XPM;

  if (DEBUG) printf("LoadXPM(): pinfo->colType is %d\n", pinfo->colType);

  sprintf(pinfo->fullInfo, "Xpm v3 Pixmap (%ld bytes)", filesize);
  sprintf(pinfo->shrtInfo, "%dx%d Xpm.", w, h);
  pinfo->comment = (char *)NULL;

  hash_destroy();
  free(clmp);

  if (fp != stdin)
    fclose(fp);

  return(1);
}


/***************************************/
static int XpmLoadError(const char *fname, const char *st)
{
  SetISTR(ISTR_WARNING, "%s:  %s", fname, st);
  return 0;
}


/***************************************/
static int XpmGetc(FILE *f)
{
  int	c, d, lastc;

  if (bufchar != -2) {
    /* The last invocation of this routine read the character... */
    c = bufchar;
    bufchar = -2;
    return(c);
  }

  if ((c = getc(f)) == EOF)
    return(EOF);

  if (c == '"')
    in_quote = !in_quote;
  else if (!in_quote && c == '/') {	/* might be a C-style comment */
    if ((d = getc(f)) == EOF)
      return(EOF);
    if (d == '*') {				/* yup, it *is* a comment */
      if ((lastc = getc(f)) == EOF)
	return(EOF);
      do {				/* skip through to the end */
	if ((c = getc(f)) == EOF)
	  return(EOF);
	if (lastc != '*' || c != '/')	/* end of comment */
	  lastc = c;
      } while (lastc != '*' || c != '/');
      if ((c = getc(f)) == EOF)
	return(EOF);
    } else					/* nope, not a comment */
      bufchar = d;
  }
  return(c);
}


/***************************************/
/*         hashing functions           */
/***************************************/


/***************************************/
static int hash(char *token)
{
  int i, sum;

  for (i=sum=0; token[i] != '\0'; i++)
    sum += token[i];

  sum = sum % hash_len;
  return (sum);
}


/***************************************/
static int hash_init(int hsize)
{
  /*
   * hash_init() - This function takes an arg, but doesn't do anything with
   * it.  It could easily be expanded to figure out the "optimal" number of
   * buckets.  But, for now, a hard-coded 257 will do.  (Until I finish the
   * 24-bit image writing code.  :-)
   */

  int i;

  XV_UNUSED(hsize);

  hash_len = 257;

  hashtab = (hentry **) malloc(sizeof(hentry *) * hash_len);
  if (!hashtab) {
    SetISTR(ISTR_WARNING, "Couldn't malloc hashtable in LoadXPM()!\n");
    return 0;
  }

  for (i = 0 ; i < hash_len ; i++)
    hashtab[i] = NULL;

  return 1;
}


/***************************************/
static int hash_insert(hentry *entry)
{
  int     key;
  hentry *tmp;

  key = hash(entry->token);

  tmp = (hentry *) malloc(sizeof(hentry));
  if (!tmp) {
    SetISTR(ISTR_WARNING, "Couldn't malloc hash entry in LoadXPM()!\n");
    return 0;
  }

  xvbcopy((char *)entry, (char *)tmp, sizeof(hentry));

  if (hashtab[key]) tmp->next = hashtab[key];
               else tmp->next = NULL;

  hashtab[key] = tmp;

  return 1;
}


/***************************************/
static hentry *hash_search(char *token)
{
  int     key;
  hentry *tmp;

  key = hash(token);

  tmp = hashtab[key];
  while (tmp && strcmp(token, tmp->token)) {
    tmp = tmp->next;
  }

  return tmp;
}


/***************************************/
static void hash_destroy(void)
{
  int     i;
  hentry *tmp;

  for (i=0; i<hash_len; i++) {
    while (hashtab[i]) {
      tmp = hashtab[i]->next;
      free(hashtab[i]);
      hashtab[i] = tmp;
    }
  }

  free(hashtab);
  return;
}



/**************************************************************************/
int WriteXPM(FILE *fp /* File to write to */,
		byte *pic /* Image data */,
		int ptype /* PIC8 or PIC24 */,
		int w, int h /* width, & height */,
		byte *rp, byte *gp, byte *bp, /* Colormap pointers */
		int nc, int col, /* number of colors, & colorstyle */
		char *name /* name of file (/image) */,
		char *comments /* image comments (not currently used) */ )
{
  /* Note here, that tokenchars is assumed to contain 64 valid token */
  /* characters.  It's hardcoded to assume this for benefit of generating */
  /* tokens, when there are more than 64^2 colors. */

  short	i, imax, j;	/* for() loop indices */
  short	cpp = 0;
  const char *tokenchars =
            ".#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  char	*tokens;
  char	image_name[256], *foo;
  byte	grey;

#ifndef USE_UNFINISHED_24BIT_WRITING_CODE
  byte	*pic8, *sptr;
  byte	rtemp[256], gtemp[256], btemp[256], hist[256], trans[256];
  long	li;		/* for() loop index */
  int	numcol;
#endif

  XV_UNUSED(comments);

  if (DEBUG) {
    if (ptype == PIC8)
      printf("WriteXPM(): Write a %d color, colortype %d, PIC8 image.\n",
	     nc, col);
    else
      printf("WriteXPM(): Write a colortype %d, PIC24 image.\n", col);
  }

  strcpy(image_name, BaseName(name));
  foo = (char *)strchr(image_name, '.');
  if (foo)
    *foo = '\0';		/* Truncate name at first '.' */

#ifdef USE_UNFINISHED_24BIT_WRITING_CODE
  if (ptype == PIC24)
    return -1;
#else
  /* XXX - The colormap returned from Conv24to8 is not a specific  */
  /* size (and Conv24to8 doesn't tell me how many entries there    */
  /* are, or not in this rev of code at least) and not necessarily */
  /* 'packed'.  Code in here to do that should be removed if       */
  /* Conv24to8 is "fixed" to do this...                            */
  /*    Chris P. Ross (cross@eng.umd.edu)  28-Sept-94              */

  numcol = 0;

  if (ptype == PIC24) {
    /* Reduce to an 8-bit image.  Would be nice to actually write */
    /* the 24-bit image.  I'll have to code that someday...       */
    pic8 = Conv24to8(pic, w, h, 256, rtemp, gtemp, btemp);
    if (!pic8) {
      SetISTR(ISTR_WARNING,
	      "%s:  Unable to convert to 8-bit image in WriteXPM()",
	      image_name);
      return 1;
    }
    if (DEBUG) printf("WriteXPM(): Converted 24bit image.\n");

    /* This will count the number of colors in use, and build an */
    /* index array into the colormap (which may not be 'packed') */
    /* Thanks to John Bradley for this code..                    */

    xvbzero((char *) hist, sizeof(hist));
    numcol = 0;

    if (DEBUG)
      printf("WriteXPM(): Counting colors (width: %d, height: %d)...\n", w, h);

    for (li = 0, sptr = pic8 ; li < (w*h) ; li++, sptr++) {
      hist[*sptr] = 1;
    }

    if (DEBUG)
      printf("WriteXPM(): building translation table...\n");
    for (i = 0 ; i < 256 ; i++) {
      trans[i] = numcol;
      rtemp[numcol] = rtemp[i];
      gtemp[numcol] = gtemp[i];
      btemp[numcol] = btemp[i];
      numcol += hist[i];
    }
    rp=rtemp; gp=gtemp; bp=btemp;
    if (DEBUG)
      printf("WriteXPM(): Converted 24bit image now has %d colors.\n", numcol);
  } else {
    pic8 = pic;
    numcol = nc;
  }
#endif


#ifdef USE_UNFINISHED_24BIT_WRITING_CODE
  if (ptype == PIC24) cpp = 4;
  else if (numcol > 64) cpp = 2;
  else cpp = 1;
#else
  if (numcol > 64) cpp = 2;
  else cpp = 1;
#endif

  fprintf(fp, "/* XPM */\n");
  fprintf(fp, "static char *%s[] = {\n", image_name);
  fprintf(fp, "/* width height num_colors chars_per_pixel */\n");
  fprintf(fp, "\"   %3d   %3d   %6d            %1d\",\n", w, h, numcol, cpp);
  fprintf(fp, "/* colors */\n");

  switch (cpp) {

  case 1:			/* <= 64 colors; index into tokenchars */
    if (col == F_GREYSCALE)
      for (i = 0 ; i < numcol ; i ++) {
	grey = MONO(rp[i], gp[i], bp[i]);
	fprintf(fp, "\"%c c #%02x%02x%02x\",\n", tokenchars[i],
		grey, grey, grey);
      }
    else
      for (i = 0 ; i < numcol ; i ++)
	fprintf(fp, "\"%c c #%02x%02x%02x\",\n", tokenchars[i],
		rp[i], gp[i], bp[i]);
    fprintf(fp, "/* pixels */\n");
    for (i = 0 ; i < h ; i ++) {
      fprintf(fp, "\"");
      if (!(i%20))
	WaitCursor();
      for (j = 0 ; j < w ; j++) {
	if (rp == rtemp)
	  fprintf(fp, "%c", tokenchars[trans[*pic8++]]);
	else
	  fprintf(fp, "%c", tokenchars[*pic8++]);
      }
      if (i < h-1)
	fprintf(fp, "\",\n");
    }
    break;

  case 2:			/* 64 < colors < 64^2; build new token list */
    tokens = (char *) malloc((size_t) ((2*numcol) + 1) );
    if (numcol & 0x3f)
      imax = (numcol >> 6) + 1;
    else
      imax = (numcol >> 6);
    for (i = 0 ; i < imax ; i++)
      for (j =  0 ; j < 64 && ((i<<6)+j) < numcol ; j++) {
	*(tokens+((i<<6)+j)*2) = tokenchars[i];
	*(tokens+((i<<6)+j)*2+1) = tokenchars[j];
      }
    if (col == F_GREYSCALE)
      for (i = 0 ; i < numcol ; i++) {
	grey = MONO(rp[i], gp[i], bp[i]);
	fprintf(fp, "\"%c%c c #%02x%02x%02x\",\n", tokens[i*2],
		tokens[i*2+1], grey, grey, grey);
      }
    else
      for (i = 0 ; i < numcol ; i++)
	fprintf(fp, "\"%c%c c #%02x%02x%02x\",\n", tokens[i*2],
		tokens[i*2+1], rp[i], gp[i], bp[i]);
    fprintf(fp, "/* pixels */\n");
    for (i = 0 ; i < h ; i++) {
      fprintf(fp, "\"");
      if (!(i%13))
	WaitCursor();
      for (j = 0 ; j < w ; j++) {
	if (rp == rtemp)
	  fprintf(fp, "%c%c", tokens[trans[*pic8]*2],
		  tokens[(trans[*pic8]*2)+1]);
	else
	  fprintf(fp, "%c%c", tokens[(*pic8*2)],
		  tokens[(*pic8*2)+1]);
	pic8++;
      }
      if (i < h-1)
	fprintf(fp, "\",\n");
    }
    break;

  case 4:
    /* Generate a colormap */

    break;
  default:
    break;
  }

  if (fprintf(fp, "\"\n};\n") == EOF) {
    return 1;
  } else
    return 0;
}




