/*****************************************************************
 * flgifr.c: FBM Release 1.0 25-Feb-90 Michael Mauldin
 *
 * Modifications to GIFTORLE are Copyright (C) 1989,1990 by Michael
 * Mauldin.  Permission is granted to use this file in whole or in
 * part for any purpose, educational, recreational or commercial,
 * provided that this copyright notice is retained unchanged.
 * This software is available to all free of charge by anonymous
 * FTP and in the UUNET archives.
 *
 * Derived from 'giftorle', written by David Koblas
 *
 * +------------------------------------------------------------------+ 
 * | Copyright 1989, David Koblas.                                    | 
 * |   You may copy this file in whole or in part as long as you      | 
 * |   don't try to make money off it, or pretend that you wrote it.  | 
 * +------------------------------------------------------------------+ 
 *
 * CONTENTS
 *	read_gif (image, stream, mstr, mlen)
 *
 * EDITLOG
 *	LastEditDate = Mon Jun 25 00:09:49 1990 - Michael Mauldin
 *	LastFileName = /usr2/mlm/src/misc/fbm/flgifc.c
 *
 * HISTORY
 * 25-Jun-90  Michael Mauldin (mlm@cs.cmu.edu) Carnegie Mellon
 *	Package for Release 1.0
 *
 * 20-Jun-89  Michael Mauldin (mlm) at Carnegie Mellon University
 *	Bug fix (version 0.95) from David Koblas <koblas@mips.com>
 *
 * 26-Apr-89  Michael Mauldin (mlm) at Carnegie Mellon University
 *	Beta release (version 0.91) mlm@cs.cmu.edu
 *
 * 19-Feb-89  Michael Mauldin (mlm) at Carnegie Mellon University
 *	Changed name to flgifr from flgif (since there is now an flgifw)
 *
 * 13-Feb-89  Michael Mauldin (mlm) at Carnegie Mellon University
 *	Modified from gif2rle program Copyright 1989 by David Koblas.
 *	Converted to produce FBM image format in memory.
 *
 *****************************************************************/

# include <stdio.h>
# include "fbm.h"

#define MAXCOLORMAPSIZE         256

#define TRUE    1
#define FALSE   0
#define BAD     0
#define OK      1

#define MAX_LWZ_BITS            12

#define ReadOK(file,buffer,len) (fread(buffer,len,1,file)!=0)

/*
 * Hacknote: The following two macros were originally written as
 *	while(1){fprintf(...); return(...);}
 * This caused a compiler warning about the ';' statement not being
 * reached when called as EasyFail(string,num);
 *
 * Using the C ',' operator, the macros can be replaced by a single
 * function call...note the extra set of parentheses to force the
 * correct interpretation of the ','		Fuzzy 20-Jun-90
 */
#define EasyFail(str,status)    return((fprintf (stderr,str), status))
#define HardFail(str,status)    exit ((fprintf(stderr,str), status))

#define LM_to_uint(a,b)                 (((b)<<8)|(a))

#ifndef lint
static char *fbmid =
"$FBM flgifr.c <1.0> 25-Jun-90  (C) 1989,1990 by Michael Mauldin, source \
code available free from MLM@CS.CMU.EDU and from UUNET archives$";
#endif

read_gif (image, fd, mstr, mlen)
FBM *image;
FILE *fd;
char *mstr;
int mlen;
{
  unsigned char   buf[16];
  unsigned char   c;
  int             use_global_colormap;
  int             bit_pixel;
  int             count = 0;
  int             rows, cols, rowlen, depth, colors;

  /* Read magic number 'GIF87a' */
  buf[0] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[1] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[2] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[3] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[4] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[5] = NEXTMCH (fd, mstr, mlen) & 0xff;
  buf[6] = 0;

  if (strcmp (buf, GIF_MAGIC) != 0)
    EasyFail("bad magic number (version mismatch?)\n", BAD);

  if (!ReadOK (fd, buf, 7))
    EasyFail("error reading screen descriptor\n", BAD);

  /* Read image data */
  image->hdr.cols = cols = LM_to_uint(buf[0], buf[1]);
  image->hdr.rows = rows = LM_to_uint(buf[2], buf[3]);
  image->hdr.planes = 1;
  image->hdr.bits = depth = (buf[4] & 0x07) + 1;
  image->hdr.physbits = 8;

  /* Pad to even byte */
  if (depth == 1)
  { image->hdr.rowlen = rowlen = 16 * ((cols + 15) / 16); }
  else
  { image->hdr.rowlen = rowlen = 2 * ((cols + 1) / 2); }

  image->hdr.plnlen = rowlen * rows;
  colors = 1 << depth;
  image->hdr.clrlen = 3 * colors;
  image->hdr.title[0] = '\0';
  image->hdr.credits[0] = '\0';
  
  /* Try to guess aspect ratio */
  if (cols == 320 && rows == 200)	{ image->hdr.aspect = 1.2; }
  else if (cols == 320 && rows == 175)	{ image->hdr.aspect = 1.2; }
  else if (cols == 320 && rows == 400)	{ image->hdr.aspect = 0.6; }
  else if (cols == 320 && rows == 350)	{ image->hdr.aspect = 0.6; }
  else if (cols == 640 && rows == 200)	{ image->hdr.aspect = 2.4; }
  else if (cols == 640 && rows == 175)	{ image->hdr.aspect = 2.4; }
  else if (cols == 640 && rows == 400)	{ image->hdr.aspect = 1.2; }
  else if (cols == 640 && rows == 350)	{ image->hdr.aspect = 1.2; }
  else					{ image->hdr.aspect = 1.0; }
  
# ifdef DEBUG
  fprintf (stderr, "\nImage file header:\n\n");
  fprintf (stderr, "cols       %d\n", image->hdr.cols);
  fprintf (stderr, "rows       %d\n", image->hdr.rows);
  fprintf (stderr, "planes     %d\n", image->hdr.planes);
  fprintf (stderr, "bits       %d\n", image->hdr.bits);
  fprintf (stderr, "physbits   %d\n", image->hdr.physbits);
  fprintf (stderr, "rowlen     %d\n", image->hdr.rowlen);
  fprintf (stderr, "plnlen     %d\n", image->hdr.plnlen);
  fprintf (stderr, "clrlen     %d\n", image->hdr.clrlen);
  fprintf (stderr, "aspect     %1.3lf\n", image->hdr.aspect);
  fprintf (stderr, "title      '%s'\n", image->hdr.title);
  fprintf (stderr, "credits    '%s'\n", image->hdr.credits);
# endif
  
  /* Allocate image */
  alloc_fbm (image);

  /* Read colormap if given */
  if ((buf[4] & 0x80) == 0x80)
  { if (ReadColorMap (fd, colors, image->cm) == BAD)
      return (BAD);
  }

  while (1)
  { if (!ReadOK (fd, &c, 1))
      EasyFail("No image data -- EOF\n", BAD);

    if (c == ';')
      return OK;

    if (c == '!')
    { if (!ReadOK (fd, &c, 1))
        EasyFail("No extention function code -- EOF\n", BAD);
      if (IgnoreExtention(fd) == BAD)
        return (BAD);
    }

    if (c != ',')
    { fprintf(stderr, "Bogus character ignoring '%c'\n", c);
      continue;
    }

    if (count == 1)
      HardFail("This file contains more than one image! FAILING\n", 1);
    count++;

    if (!ReadOK (fd, buf, 9))
      EasyFail("Couldn't read left/top/width/height\n", TRUE);

# ifdef DEBUG
    fprintf (stderr, "Image description: ");
    for (i=0; i<9; i++) fprintf (stderr, "%02x", buf[i]);
    fprintf (stderr, "\n");
# endif

    if ((buf[8] & 0x80) == 0x80)
      use_global_colormap = FALSE;
    else
      use_global_colormap = TRUE;

    bit_pixel = 2 << (buf[8] & 0x07);
    
    if (!use_global_colormap && bit_pixel > colors)
    { HardFail ("Local colormap has more colors than global!\n", BAD); }

    if (!use_global_colormap)
    { fprintf (stderr,
               "Overriding global colormap (%d) with local (%d)\n",
               colors, bit_pixel);

      colors = bit_pixel;
      image->hdr.clrlen = 3 * colors;
      
      if (ReadColorMap(fd, colors, image->cm) == BAD)
        return BAD;
    }

    fprintf (stderr, "Reading GIF image [%dx%d], %d bits",
	     image->hdr.cols, image->hdr.rows, image->hdr.bits);
    if (image->hdr.clrlen > 0)
    { fprintf (stderr, ", %d colors", image->hdr.clrlen / 3); }
    else if (image->hdr.bits > 1)
    { fprintf (stderr, ", grayscale"); }

    if ((buf[8] & 0x40) == 0x40)
    { fprintf (stderr, ", interlaced"); }
    fprintf (stderr, "\n");


    if ((buf[8] & 0x40) == 0x40)
    { 
      if (ReadInterlaced(fd,
			 LM_to_uint(buf[4],buf[5]),
			 LM_to_uint(buf[6],buf[7]),
			 image->bm,
			 rowlen) == BAD)
	return BAD;
    }
    else
    { if (ReadRaster(fd,
                     LM_to_uint(buf[4], buf[5]),
                     LM_to_uint(buf[6], buf[7]),
                     image->bm,
                     rowlen) == BAD)
        return BAD;
      else
        return OK;
    }
  }
}

ReadColorMap (fd, number, buffer)
  FILE           *fd;
  int             number;
  unsigned char   *buffer;
{
  int             i;
  unsigned char   rgb[3], *red, *grn, *blu;

  red = buffer;
  grn = buffer + number;
  blu = grn + number;

  for (i = 0; i < number; i++)
  { if (!ReadOK (fd, rgb, sizeof(rgb)))
      EasyFail("Bogus colormap\n", BAD);

    red[i] = rgb[0];
    grn[i] = rgb[1];
    blu[i] = rgb[2];
  }

  return OK;
}

IgnoreExtention(fd)
  FILE           *fd;
{
  static char     buf[256];
  unsigned char   c;

  while (1)
  {
    if (!ReadOK (fd, &c, 1))
      EasyFail("EOF in extention\n", BAD);

    if (c == 0)
      return OK;

    if (read(fd, buf, (int) c) != (int) c)
      EasyFail("EOF in extention\n", BAD);
  }
}

GetCode(fd, code_size, flag)
  FILE           *fd;
  int             code_size;
  int             flag;
{
  static unsigned char buf[280];
  static int      curbit, lastbit, done, last_byte;
  int             i, j, ret;
  unsigned char   count;

  if (flag)
  {
    curbit = 0;
    lastbit = 0;
    done = FALSE;
    return 0;
  }

  if ((curbit + code_size) >= lastbit)
  { if (done)
    { if (curbit >= lastbit)
        EasyFail("Ran off the end of my bits\n", -1);
    }
    buf[0] = buf[last_byte - 2];
    buf[1] = buf[last_byte - 1];

    if (!ReadOK (fd, &count, 1))
    { EasyFail("Error in getting buffer size\n", -1); }

    if (count == 0)
    { done = TRUE; } 

    else if (!ReadOK (fd, &buf[2], count))
      EasyFail("Error in getting buffer\n", -1);

    last_byte = 2 + count;
    curbit = (curbit - lastbit) + 16;
    lastbit = (2 + count) * 8;
  }

  ret = 0;

  for (i = curbit, j = 0; j < code_size; i++, j++)
    ret |= ((buf[i / 8] & (1 << (i % 8))) != 0) << j;

  curbit += code_size;

  return ret;
}

LWZReadByte(fd, flag, input_code_size)
  FILE           *fd;
  int             flag;
  int             input_code_size;
{
  static int      fresh = FALSE;
  int             code, incode;
  static int      code_size, set_code_size;
  static int      max_code, max_code_size;
  static int      firstcode, oldcode;
  static int      clear_code, end_code;
  static int      table[2][(1 << MAX_LWZ_BITS)];
  static int      stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
  register int    i;

  if (flag)
  {
    set_code_size = input_code_size;
    code_size = set_code_size + 1;
    clear_code = 1 << set_code_size;
    end_code = clear_code + 1;
    max_code_size = 2 * clear_code;
    max_code = clear_code + 2;

    GetCode(fd, 0, TRUE);

    fresh = TRUE;

    for (i = 0; i < clear_code; i++)
    {
      table[0][i] = 0;
      table[1][i] = i;
    }

    for (; i < (1 << MAX_LWZ_BITS); i++)
      table[0][i] = table[1][0] = 0;

    sp = stack;
    return 0;
  }
  else if (fresh)
  { fresh = FALSE;
    do
    { firstcode = oldcode =
        GetCode(fd, code_size, FALSE);
    } while (firstcode == clear_code);

    return firstcode;
  }

  if (sp > stack)
    return *--sp;

  while ((code = GetCode(fd, code_size, FALSE)) >= 0)
  { if (code == clear_code)
    { for (i = 0; i < clear_code; i++)
      {table[0][i] = 0;
        table[1][i] = i;
      }

      for (; i < (1 << MAX_LWZ_BITS); i++)
        table[0][i] = table[1][i] = 0;

      code_size = set_code_size + 1;
      max_code_size = 2 * clear_code;
      max_code = clear_code + 2;
      sp = stack;
      firstcode = oldcode =
        GetCode(fd, code_size, FALSE);
      return firstcode;
    }
    else if (code == end_code)
    { unsigned char   count;
      unsigned char   junk;

      while (ReadOK (fd, &count, 1) && (count != 0))
        while (count-- != 0 && ReadOK (fd, &junk, 1));

      if (count != 0)
        EasyFail("missing EOD in data stream (common occurance)\n", -3);

      return -2;
    }

    incode = code;

    if (code >= max_code)
    { *sp++ = firstcode;
      code = oldcode;
    }

    while (code >= clear_code)
    {
      *sp++ = table[1][code];
      if (code == table[0][code])
        EasyFail("Circular table entry BIG ERROR\n", -1);
      code = table[0][code];
    }

    *sp++ = firstcode = table[1][code];

    if ((code = max_code) < (1 << MAX_LWZ_BITS))
    {
      table[0][code] = oldcode;
      table[1][code] = firstcode;
      max_code++;

      if ((max_code >= max_code_size) &&
          (max_code_size < (1 << MAX_LWZ_BITS)))
      {
        max_code_size *= 2;
        code_size++;
      }
    }
    oldcode = incode;

    if (sp > stack)
      return *--sp;
  }

  return code;
}

ReadInterlaced(fd, len, height, buffer, rowlen)
  FILE           *fd;
  int             len, height;
  unsigned char	 *buffer;
  int		  rowlen;
{
  unsigned char   c;
  register unsigned char *bmp;
  register int    v;
  register int    xpos = 0;
  register int    ypos = 0, pass = 0;
  register int    maxypos = 0;


  if (!ReadOK(fd, &c, 1))
    EasyFail("Bogus image data -- EOF\n", BAD);
  if (LWZReadByte(fd, TRUE, c) < 0)
    return BAD;

  while ((v = LWZReadByte(fd, FALSE, c)) >= 0)
  {
    if (xpos == 0)
    { bmp = &(buffer[ypos * rowlen]);
      if (ypos > height)
      { fprintf (stderr, "Wanring Too much data, started to read line %d\n",
		 ypos);
        return (OK);
      }
    }
    
    *bmp++ = v;

    if (++xpos == len)
    {
      xpos = 0;
      switch (pass)
      {
       case 0: case 1:		ypos += 8; break;
       case 2:			ypos += 4; break;
       case 3:			ypos += 2; break;
      }
      
      if (ypos > maxypos)	maxypos = ypos;

      if (ypos >= height)
      {
	switch (++pass)
	{
	 case 1:		ypos = 4; break;
	 case 2:		ypos = 2; break;
	 case 3:		ypos = 1; break;
	}
      }
    }
  }

  if (maxypos >= height) return OK;

  if (v == (-2))
    return OK;
  return BAD;
}

ReadRaster (fd, len, height, buffer, rowlen)
  FILE           *fd;
  int             len, height;
  unsigned char  *buffer;
  int             rowlen;
{
  unsigned char   c;
  register unsigned char  *bmp;
  register int    v;
  register int    xpos = 0;
  register int    ypos = 0;

  if (!ReadOK (fd, &c, 1))
    EasyFail("Bogus image data -- EOF\n", TRUE);

  if (LWZReadByte(fd, TRUE, c) < 0)
    return BAD;

  /* Read the raster data and dump it into the FBM bitmap */
  while ((v = LWZReadByte(fd, FALSE, c)) >= 0)
  {
    if (xpos == 0)
    { bmp = &(buffer[ypos++ * rowlen]);
      if (ypos > height)
      { fprintf (stderr, "Warning: too much data, started to read line %d\n",
		 ypos);
        return (OK);
      }
    }
    
    *bmp++ = v;
    
    if (++xpos == len)

    if (xpos == len) xpos = 0;
  }

  fprintf (stderr, "Done storing bitmap, xpos %d, ypos %d [%dx%d]\n",
           xpos, ypos, len, height);

  if (ypos >= height) return OK;

  if (v == (-2))
    return OK;
  return BAD;
}
