/* gif.c:
 *
 * adapted from code by kirk johnson (tuna@athena.mit.edu).  most of this
 * code is unchanged. -- jim frost 12.31.89
 *
 * gifin.c
 * kirk johnson
 * november 1989
 *
 * routines for reading GIF files
 *
 * Copyright 1989 Kirk L. Johnson (see the included file
 * "kljcpyrght.h" for complete copyright information)
 */

#define GIF_C
#include "kljcpyrght.h"
#include "xli.h"
#include "gif.h"

/****
 **
 ** local #defines
 **
 ****/

#define PUSH_PIXEL(p)                                       \
{                                                           \
  if (pstk_idx == PSTK_SIZE)                                \
    return GIFIN_ERR_PSO;                                   \
  else                                                      \
    pstk[pstk_idx++] = (p);                                 \
}

/*
 * push a string (denoted by a code) onto the pixel stack
 * (returns the code of the first pixel in the string in ps_rslt)
 */

int ps_rslt;	/* return result */

#define GIFIN_PUSH_STRING(code)                             \
{                                                           \
  int ps_code;                                              \
  ps_code = code;                                           \
                                                            \
  while (((unsigned int)ps_code) < STAB_SIZE && prefix[ps_code] != NULL_CODE) \
  {                                                         \
    PUSH_PIXEL(extnsn[ps_code]);                            \
    ps_code = prefix[ps_code];                              \
  }                                                         \
                                                            \
  if (((unsigned int)ps_code) >= STAB_SIZE)                                 \
    return GIFIN_ERR_TAO;                                   \
  PUSH_PIXEL(extnsn[ps_code]);                              \
  ps_rslt = extnsn[ps_code];                                \
  if (((unsigned int)ps_rslt) >= STAB_SIZE)                                 \
    return GIFIN_ERR_TAO;                                   \
}

/*
 * Look up the ascii message coresponding to
 * the error number.
 */
static char *
get_err_string(int errno)
{
  int i;
  for (i=0;gif_err_strings[i].err_no != 0;i++)
  {
    if (gif_err_strings[i].err_no == errno)
		return gif_err_strings[i].name;
  }
  return "";
}

/****
 **
 ** local variables
 **
 ****/

static int interlace_start[4]= { /* start line for interlacing */
  0, 4, 2, 1
};

static int interlace_rate[4]= { /* rate at which we accelerate vertically */
  8, 8, 4, 2
};

static BYTE file_open  = 0;     /* status flags */
static BYTE image_open = 0;

static ZFILE *ins;              /* input stream */

static int  root_size;          /* root code size */
static int  clr_code;           /* clear code */
static int  eoi_code;           /* end of information code */
static int  code_size;          /* current code size */
static int  code_mask;          /* current code mask */
static int  prev_code;          /* previous code */

/*
 * NOTE: a long is assumed to be at least 32 bits wide
 */
static long work_data;          /* working bit buffer */
static int  work_bits;          /* working bit count */

static BYTE buf[256];           /* byte buffer */
static int  buf_cnt;            /* byte count */
static int  buf_idx;            /* buffer index */

static int table_size;          /* string table size */
static int prefix[STAB_SIZE];   /* string table : prefixes */
static int extnsn[STAB_SIZE];   /* string table : extensions */

static BYTE pstk[PSTK_SIZE];    /* pixel stack */
static int  pstk_idx;           /* pixel stack pointer */


/****
 **
 ** global variables
 **
 ****/

static int  gifin_rast_width;          /* raster width */
static int  gifin_rast_height;         /* raster height */
static BYTE gifin_g_cmap_flag;         /* global colormap flag */
static int  gifin_g_pixel_bits;        /* bits per pixel, global colormap */
static int  gifin_g_ncolors;           /* number of colors, global colormap */
static BYTE gifin_g_cmap[3][256];      /* global colormap */
static BYTE gifin_g_cmap_sorted;       /* global colormap sorted (GIF89a only) */
static int  gifin_bg_color;            /* background color index */
static int  gifin_color_bits;          /* bits of color resolution */
static double  gifin_aspect;           /* pixel aspect ratio (width/height) */
static int  gifin_version;             /* gif file version */

static int  gifin_img_left;            /* image position on raster */
static int  gifin_img_top;             /* image position on raster */
static int  gifin_img_width;           /* image width */
static int  gifin_img_height;          /* image height */
static BYTE gifin_l_cmap_flag;         /* local colormap flag */
static int  gifin_l_pixel_bits;        /* bits per pixel, local colormap */
static int  gifin_l_ncolors;           /* number of colors, local colormap */
static BYTE gifin_l_cmap[3][256];      /* local colormap */
static BYTE gifin_interlace_flag;      /* interlace image format flag */

/*
 * load a colormap from the input stream
 */

static int gifin_load_cmap(BYTE (*cmap)[256], int ncolors)
{
  int i;

  for (i=0; i<ncolors; i++)
  {
    if (zread(ins, buf, 3) != 3)
      return GIFIN_ERR_EOF;
    
    cmap[GIF_RED][i] = buf[GIF_RED];
    cmap[GIF_GRN][i] = buf[GIF_GRN];
    cmap[GIF_BLU][i] = buf[GIF_BLU];
  }

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * open a GIF file, using s as the input stream
 */

static int gifin_open_file(ZFILE *s, boolean verbose)
{
  int errno;
  /* make sure there isn't already a file open */
  if (file_open)
    return GIFIN_ERR_FAO;

  /* remember that we've got this file open */
  file_open = 1;
  ins       = s;

  /* check GIF signature */
  if (zread(ins, buf, GIF_SIG_LEN) != GIF_SIG_LEN)
    return GIFIN_ERR_EOF;

  buf[GIF_SIG_LEN] = '\0';
  if (strcmp((char *) buf, GIF_SIG) == 0)
    gifin_version = GIF87a;
  else if(strcmp((char *) buf, GIF_SIG_89) == 0)
    gifin_version = GIF89a;
  else
    return GIFIN_ERR_BAD_SIG;

  /* read screen descriptor */
  if (zread(ins, buf, GIF_SD_SIZE) != GIF_SD_SIZE)
    return GIFIN_ERR_EOF;

  /* decode screen descriptor */
  gifin_rast_width   = (buf[1] << 8) + buf[0];
  gifin_rast_height  = (buf[3] << 8) + buf[2];
  gifin_g_cmap_flag  = (buf[4] & 0x80) ? 1 : 0;
  gifin_color_bits   = (((int)(buf[4] & 0x70)) >> 4) + 1;
  gifin_g_pixel_bits = (buf[4] & 0x07) + 1;
  gifin_bg_color     = buf[5];
  gifin_aspect = 1.0;

  if (gifin_version == GIF87a) {
    if (buf[4] & 0x08 || buf[6] != 0) {
      if (verbose) {
	fprintf(stderr, "gifLoad: ignoring non-null screen descriptor in Gif87a image\n");
      }
    }
  } else {
    gifin_g_cmap_sorted = ((buf[4] & 0x08) != 0);
    if (buf[6] != 0)
      gifin_aspect = ((double)buf[6] + 15.0) / 64.0;
  }

  /* load global colormap */
  if (gifin_g_cmap_flag)
  {
    gifin_g_ncolors = (1 << gifin_g_pixel_bits);

    if ((errno = gifin_load_cmap(gifin_g_cmap, gifin_g_ncolors)) != GIFIN_SUCCESS)
      return errno;
  }
  else
  {
    gifin_g_ncolors = 0;
  }

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * read a new data block from the input stream
 */

static int gifin_read_data_block(void)
{
  /* read the data block header */
  buf_cnt = zgetc(ins);

  /* read the data block body */
  if (zread(ins, buf, buf_cnt) != buf_cnt)
    return GIFIN_ERR_EOF;

  buf_idx = 0;

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * skip an extension block in the input stream
 */

static int gifin_skip_extension(void)
{
  int errno;

  /* get the extension function byte */
  zgetc(ins);

  /* skip any remaining raster data */
  do
  {
    if ((errno = gifin_read_data_block()) != GIFIN_SUCCESS)
      return errno;
  }
  while (buf_cnt > 0);

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * open next GIF image in the input stream; returns GIFIN_SUCCESS if
 * successful. if there are no more images, returns GIFIN_DONE. (might
 * also return various GIFIN_ERR codes.)
 */

static int gifin_open_image(void)
{
  int i;
  int separator;
  int errno;

  /* make sure there's a file open */
  if (!file_open)
    return GIFIN_ERR_NFO;

  /* make sure there isn't already an image open */
  if (image_open)
    return GIFIN_ERR_IAO;

  /* remember that we've got this image open */
  image_open = 1;

  /* skip over any extension blocks */
  do
  {
    separator = zgetc(ins);
    if (separator == GIF_EXTENSION)
    {
      if ((errno = gifin_skip_extension()) != GIFIN_SUCCESS)
        return errno;
    }
  }
  while (separator == GIF_EXTENSION);

  /* check for end of file marker */
  if (separator == GIF_TERMINATOR)
    return GIFIN_DONE;

  /* make sure we've got an image separator */
  if (separator != GIF_SEPARATOR)
    return GIFIN_ERR_BAD_SEP;

  /* read image descriptor */
  if (zread(ins, buf, GIF_ID_SIZE) != GIF_ID_SIZE)
    return GIFIN_ERR_EOF;

  /* decode image descriptor */
  gifin_img_left       = (buf[1] << 8) + buf[0];
  gifin_img_top        = (buf[3] << 8) + buf[2];
  gifin_img_width      = (buf[5] << 8) + buf[4];
  gifin_img_height     = (buf[7] << 8) + buf[6];
  gifin_l_cmap_flag    = (buf[8] & 0x80) ? 1 : 0;
  gifin_interlace_flag = (buf[8] & 0x40) ? 1 : 0;
  gifin_l_pixel_bits   = (buf[8] & 0x07) + 1;

  /* load local colormap */
  if (gifin_l_cmap_flag)
  {
    gifin_l_ncolors = (1 << gifin_l_pixel_bits);

    if ((errno = gifin_load_cmap(gifin_l_cmap, gifin_l_ncolors)) != GIFIN_SUCCESS)
      return errno;
  }
  else
  {
    gifin_l_ncolors = 0;
  }

  /* initialize raster data stream decoder */
  root_size = zgetc(ins);
  clr_code  = 1 << root_size;
  eoi_code  = clr_code + 1;
  code_size = root_size + 1;
  code_mask = (1 << code_size) - 1;
  work_bits = 0;
  work_data = 0;
  buf_cnt   = 0;
  buf_idx   = 0;

  /* initialize string table */
  for (i=0; i<STAB_SIZE; i++)
  {
    prefix[i] = NULL_CODE;
    extnsn[i] = i;
  }

  /* initialize pixel stack */
  pstk_idx = 0;

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * add a new string to the string table
 */

static int gifin_add_string(int p, int e)
{
  prefix[table_size] = p;
  extnsn[table_size] = e;

  if ((table_size == code_mask) && (code_size < 12))
  {
    code_size += 1;
    code_mask  = (1 << code_size) - 1;
  }

  table_size += 1;
  if (table_size > STAB_SIZE)
    return GIFIN_ERR_TAO;
  return GIFIN_SUCCESS;
}


/*
 * try to read next pixel from the raster, return result in *pel
 */

static int gifin_get_pixel(int *pel)
{
  int  code;
  int  first;
  int  place;
  int  errno;

  /* decode until there are some pixels on the pixel stack */
  while (pstk_idx == 0)
  {
    /* load bytes until we have enough bits for another code */
    while (work_bits < code_size)
    {
      if (buf_idx == buf_cnt)
      {
        /* read a new data block */
        if ((errno = gifin_read_data_block()) != GIFIN_SUCCESS)
          return errno;

        if (buf_cnt == 0)
          return GIFIN_ERR_EOD;
      }

      work_data |= ((long) buf[buf_idx++]) << work_bits;
      work_bits += 8;
    }

    /* get the next code */
    code        = work_data & code_mask;
    work_data >>= code_size;
    work_bits  -= code_size;

    /* interpret the code */
    if (code == clr_code)
    {
      /* reset decoder stream */
      code_size  = root_size + 1;
      code_mask  = (1 << code_size) - 1;
      prev_code  = NULL_CODE;
      table_size = eoi_code + 1;
    }
    else if (code == eoi_code)
    {
      /* Ooops! no more pixels */
      return GIFIN_ERR_EOF;
    }
    else if (prev_code == NULL_CODE)
    {
      GIFIN_PUSH_STRING(code);
      prev_code = code;
    }
    else
    {
      if (code < table_size)
      {
        GIFIN_PUSH_STRING(code);
        first = ps_rslt;
      }
      else
      {
        place = pstk_idx;
        PUSH_PIXEL(NULL_CODE);
        GIFIN_PUSH_STRING(prev_code);
        first = ps_rslt;
        pstk[place] = first;
      }

      if((errno = gifin_add_string(prev_code, first)) != GIFIN_SUCCESS)
        return errno;
      prev_code = code;
    }
  }

  /* pop a pixel off the pixel stack */
  *pel = (int) pstk[--pstk_idx];

  /* done! */
  return GIFIN_SUCCESS;
}


/*
 * close an open GIF file
 */

static int gifin_close_file(void)
{
  /* make sure there's a file open */
  if (!file_open)
    return GIFIN_ERR_NFO;

  /* mark file (and image) as closed */
  file_open  = 0;
  image_open = 0;

  /* done! */
  return GIFIN_SUCCESS;
}


/* these are the routines added for interfacing to xli
 */

/* tell someone what the image we're loading is.  this could be a little more
 * descriptive but I don't care
 */

static void tellAboutImage(char *name)
{
  printf("%s is a %dx%d %s%s image with %d colors\n",
	 name, gifin_img_width, gifin_img_height,
	 (gifin_interlace_flag ? "interlaced " : ""),
	 gif_version_name[gifin_version],
	 (gifin_l_cmap_flag ? gifin_l_ncolors : gifin_g_ncolors));
}


Image *gifLoad(char *fullname, ImageOptions *image_ops, boolean verbose)
{ ZFILE        *zf;
  char         *name = image_ops->name;
  Image *image;
  int    x, y, pixel, pass, scanlen;
  byte  *pixptr, *pixline;
  int errno;

  if (! (zf= zopen(fullname))) {
    perror("gifLoad");
    return(NULL);
  }
  if ((gifin_open_file(zf, verbose) != GIFIN_SUCCESS) || /* read GIF header */
      (gifin_open_image() != GIFIN_SUCCESS)) {  /* read image header */
    gifin_close_file();
    zclose(zf);
    return(NULL);
  }
  if (verbose)
    tellAboutImage(name);
  znocache(zf);
  image= newRGBImage(gifin_img_width, gifin_img_height, (gifin_l_cmap_flag ?
							 gifin_l_pixel_bits :
							 gifin_g_pixel_bits));
  image->title= dupString(name);
  /* if image has a local colormap, override global colormap
   */

  if (gifin_l_cmap_flag) {
    for (x= 0; x < gifin_l_ncolors; x++) {
      image->rgb.red[x]= gifin_l_cmap[GIF_RED][x] << 8;
      image->rgb.green[x]= gifin_l_cmap[GIF_GRN][x] << 8;
      image->rgb.blue[x]= gifin_l_cmap[GIF_BLU][x] << 8;
    }
    image->rgb.used= gifin_l_ncolors;
  } else {
    for (x= 0; x < gifin_g_ncolors; x++) {
      image->rgb.red[x]= gifin_g_cmap[GIF_RED][x] << 8;
      image->rgb.green[x]= gifin_g_cmap[GIF_GRN][x] << 8;
      image->rgb.blue[x]= gifin_g_cmap[GIF_BLU][x] << 8;
    }
    image->rgb.used= gifin_g_ncolors;
   }


  /* interlaced image -- futz with the vertical trace.  i wish i knew what
   * kind of drugs the GIF people were on when they decided that they
   * needed to support interlacing.
   */

  if (gifin_interlace_flag) {
    scanlen= image->height * image->pixlen;

    /* interlacing takes four passes to read, each starting at a different
     * vertical point.
     */

    for (pass= 0; pass < 4; pass++) {
      y= interlace_start[pass];
      scanlen= image->width * image->pixlen * interlace_rate[pass];
      pixline= image->data + (y * image->width * image->pixlen);
      while (y < gifin_img_height) {
	pixptr= pixline;
	for (x= 0; x < gifin_img_width; x++) {
	  if ((errno = gifin_get_pixel(&pixel)) != GIFIN_SUCCESS) {
	    fprintf(stderr, "gifLoad: %s - Short read within image data, '%s'\n", name, get_err_string(errno));
	    y = gifin_img_height; x = gifin_img_width;
	  }
	  valToMem(pixel, pixptr, image->pixlen);
	  pixptr += image->pixlen;
	}
	y += interlace_rate[pass];
	pixline += scanlen;
      }
    }
  }

  /* not an interlaced image, just read in sequentially
   */

  else {
    if(image->pixlen == 1) {	/* the usual case */
      pixptr= image->data;
      for (y= 0; y < gifin_img_height; y++)
        for (x= 0; x < gifin_img_width; x++) {
          if ((errno = gifin_get_pixel(&pixel)) != GIFIN_SUCCESS) {
	    fprintf(stderr, "gifLoad: %s - Short read within image data, '%s'\n", name, get_err_string(errno));
            y = gifin_img_height; x = gifin_img_width;
          }
          valToMem(pixel, pixptr, 1);
          pixptr += 1;
        }
    }
    else {	/* less ususal case */
      pixptr= image->data;
      for (y= 0; y < gifin_img_height; y++)
        for (x= 0; x < gifin_img_width; x++) {
          if ((errno = gifin_get_pixel(&pixel)) != GIFIN_SUCCESS) {
	    fprintf(stderr, "gifLoad: %s - Short read within image data, '%s'\n", name, get_err_string(errno));
            y = gifin_img_height; x = gifin_img_width;
          }
          valToMem(pixel, pixptr, image->pixlen);
          pixptr += image->pixlen;
        }
     }
  }
  gifin_close_file();
  read_trail_opt(image_ops,zf,image,verbose);
  zclose(zf);
  if (gifin_aspect != 1.0) {	/* correct for GIF89a aspect ratio */
    Image *timage;
    printf("  Correcting for GIF89a pixel aspect ratio of %2.4f...",gifin_aspect);
    if (gifin_aspect < 1.0)	/* tall pixels - image will look too short */
      timage = zoom(image, 0, (int)(100.0/gifin_aspect + 0.5), FALSE, FALSE);
    else	/* wide pixels - image will look too thin */
      timage = zoom(image, (int)(100.0 * gifin_aspect + 0.5), 0, FALSE, FALSE);
    if (timage != image)
      freeImage(image);
    image = timage;
    printf("done\n");
  }
  return(image);
}


int gifIdent(char *fullname, char *name)
{ ZFILE        *zf;
  unsigned int  ret;

  if (! (zf= zopen(fullname)))
    return(0);
  if ((gifin_open_file(zf, FALSE) == GIFIN_SUCCESS) &&
      (gifin_open_image() == GIFIN_SUCCESS)) {
    tellAboutImage(name);
    ret= 1;
  }
  else
    ret= 0;
  gifin_close_file();
  zclose(zf);
  return(ret);
}
