/* experimental gif reader, learning .... */
/* 9/99 - added support for 3-D (animated) GIF's */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "ana_structures.h"
 extern struct sym_desc sym[];
 struct GIFScreen {
        char id[6];
        unsigned char width_lsb;
        unsigned char width_msb;
        unsigned char height_lsb;
        unsigned char height_msb;
        unsigned char mask;
        unsigned char background;
        unsigned char extra;
 } ;
 struct GIFImage {
        char sep;
        unsigned char left_lsb;
        unsigned char left_msb;
        unsigned char top_lsb;
        unsigned char top_msb;
        unsigned char width_lsb;
        unsigned char width_msb;
        unsigned char height_lsb;
        unsigned char height_msb;
        unsigned char mask;
 } ;
 struct SingleImage {	/* something simple for the address and some info
 			about each image, note that the type is fixed in the
 			main header for the array of images */
 	char	*pointer;
 	int	bsize;
 	int	interleave;
 	int	ix, iy, nx, ny;	/*usually not used for gif's but could happen */
 } ;
 struct ImageArray {
 	int	count;
 	char	type[4];
 	struct	SingleImage	*images;
 } ;
 /* define a single one for initial usage, may want to make this a new symbol
 class eventually */

 struct ImageArray *gifset;  /* will be initially NULL */

#define min(x,y) ((x) < (y) ? (x) : (y))
#define FALSE 0
#define TRUE 1

 typedef int bool;
 typedef struct codestruct {
            struct codestruct *prefix;
            unsigned char first,suffix;
        } codetype;
 codetype *codetable;                /* LZW compression code data */
 int datasize,codesize,codemask;     /* Decoder working variables */
 int clear,eoi;                      /* Special code values */
 
 static	void readextension(), readimage(), loadcolortable(), readraster();
 static	void gif_free(), loadraster();
 static	int	quit = 0, status = 1, gcmflag;
 static int	ncolmap, cmsym, nxs, nys, gif_loop, background_color;
 /*------------------------------------------------------------------------- */
void gif_free(iarray)
 /* free up all the stuff malloc'ed in an ImageArray */
 struct ImageArray *iarray;
 {
 int	i;
 if (iarray == NULL) return;
 /* anything in the image array ? */
 if (iarray->images) {
  /* find and free them */
  for (i=0; i< iarray->count; i++) {
    char	*p = iarray->images[i].pointer;
   if (p) free(p); }
  free(iarray->images);
 }
 free(iarray);
 iarray = NULL;
 return;
 }
 /*------------------------------------------------------------------------- */
int ana_gifread_f(narg, ps)
 /* a function version that returns 1 if read OK */
 int	narg, ps[];
 {
 if ( ana_gifread(narg, ps) == 1 ) return 1; else return 4;
 }
 /*------------------------------------------------------------------------- */
int ana_gifread(narg,ps)       /* gifread subroutine */
 /* read a "simple" gif file, 8 bit deep */
 /* call is gifread,array,file,map where map is the color map, if
 map not in argument list, you don't get it! */
 int    narg, ps[];
 {
 int    result_sym, cmsym;
 char   *name;
 
 /* first arg is the variable to load, second is name of file */
 if ( sym[ ps[1] ].class != 2 ) return execute_error(70);
 name = (char *) sym[ps[1] ].spec.array.ptr;
 result_sym = ps[0];
 /* ck if output colormap wanted, set cmsym = 0 if not */
 if (narg > 2) { cmsym = ps[2]; } else cmsym = 0;

 return gif_input(name, result_sym, cmsym);
 }
 /*------------------------------------------------------------------------- */
int ana_gifmemload(narg,ps)       /* gif3dmemload subroutine */
 /* for loading 3D gif files into memory for decompress as you play
 call is gif3dmemload,file,map where map is the color map, if
 map not in argument list, you don't get it! */
 int    narg, ps[];
 {
 int    result_sym, cmsym;
 char   *name;
 
 if ( sym[ ps[0] ].class != 2 ) return execute_error(70);
 name = (char *) sym[ps[0] ].spec.array.ptr;
 /* ck if output colormap wanted, set cmsym = 0 if not */
 if (narg > 1) { cmsym = ps[1]; } else cmsym = 0;
 result_sym = NULL;

 return gif_input(name, result_sym, cmsym);
 }
 /*------------------------------------------------------------------------- */
int ana_getimage(narg,ps)       /* getimage subroutine */
 /* for decompressing gif images from memory for decompress as you play,
 return the image as a 2-D array in a passed symbol.
 The call is get_image, im, x where x is the image array and im is the # */
 int    narg, ps[];
 {
 /* presently this is hardwired for gif only, but may want to extend to
 other types of compressed data. Uses gifset structure. */
 int	im, dim[2], result_sym, nsize;
 char	*p, *data;
 struct ahead   *h;
 /* first (and currently only) argument is the image # */
 if (gifset == NULL) { printf("no gif set in memory\n"); return -1; }
 if (int_arg_stat(ps[0], &im) != 1) return -1;
 result_sym = ps[1];
 /* check if in range */
 if (im <0 ||im >= gifset->count) {
 	printf("image number %d in getimage is out of range 0-%d\n", im,
 		gifset->count -1 );  return -1; }
 //printf("im = %d\n", im);
 /* get the pointer and the array */
 p = gifset->images[im].pointer;
 //printf("image size = %d x %d\n", gifset->images[im].nx, gifset->images[im].ny);
 dim[0] = gifset->images[im].nx;
 dim[1] = gifset->images[im].ny;
 nsize = dim[0] * dim[1];
 if ( redef_array(result_sym, 0, 2, dim) != 1) {
 	return -1; }
 h = (struct ahead *) sym[result_sym].spec.array.ptr;
 data = ((char *)h + sizeof(struct ahead));
 loadraster(nsize, p, data, gifset->images[im].bsize);

 return 1;
 }
 /*------------------------------------------------------------------------- */
int gif_input(name, result_sym, cmsym)
 /* internal, used by ana_gifread and ana_gifmemload, name is the name of
 the gif file, result_sym is a symbol number for gifread or NULL for gifmemload,
 cmsym is the symbol for the color map (NULL if not desired), if multiple
 loads are needed in the future, an additional parameter can be added */
 int	result_sym, cmsym;
 char	*name;
 {
 FILE *fopen(), *fin;
 int     n, nc, nd, j, type, i, cr, pixel, gsortflag, memloadflag;
 int    vn, sep, dim[3], bad, nim = 0, flag_3d = 0, max_count, stat;
 byte   codesize=8, bq=0, ecode;
 char   *p, *q, *data, *scratch;
 struct ahead   *h;
 struct GIFScreen gh;

 /* try to open the file */
 if ((fin=fopen(name,"r")) == NULL) return file_open_error();
 
 if (result_sym) memloadflag = 0; else memloadflag =1;
 /* gif files must have a 6 byte signature followed by a screen descriptor and
 normally followed by a global color map, the first 2 of these total 15 bytes*/

 if (fread(&gh,1,13,fin) != 13) { perror("gifread in header");
 		fclose(fin); return -1; }
 if (strncmp((gh.id),"GIF",3) != 0) {
 	printf("not a GIF file\n"); return -1; }
 if (strncmp(((gh.id))+3,"87a",3) != 0) {
 	if (strncmp(((gh.id))+3,"89a",3) != 0) {
 	printf("invalid GIF version #\n"); return -1; } else {
	vn = 89;
	printf("version 89a, warning, not all options supported\n"); }
	} else vn = 87;
 /* yank out the screen size */
 nxs = ( (gh.width_msb << 8) | gh.width_lsb );
 nys = ( (gh.height_msb << 8) | gh.height_lsb );
 /* printf("screen size %d %d\n", nxs, nys); */
 if (nxs*nys <= 0) {
 	printf("GIF screen size %d is illegal\n", nxs*nys);
 	fclose(fin); return -1; }

 /* check if we are doing a mem load or an array */
 if (memloadflag) {
  gifset = (struct ImageArray *) malloc(sizeof(struct ImageArray));
  /* allocate 64 pointers, will be extended as needed */
  max_count = 64;
  gifset->images = malloc(max_count*sizeof(struct SingleImage));
  /* also a scratch buffer for loading the compressed data */
  scratch = malloc(nxs*nys + 64);
  /* check both for errors */
  if ( !(gifset->images) || !scratch)
  	{ printf("malloc failure 1 in gifmemload\n");
 	  fclose(fin); return -1; }
  gifset->count = 0;
 } else {
  /* define the output array as a byte of the screen size */

  /* if we end up with a 3-D file, this will be extended */
  dim[0] = nxs;	dim[1] = nys;
  if ( redef_array(result_sym, 0, 2, dim) != 1) { fclose(fin); return -1; }
  h = (struct ahead *) sym[result_sym].spec.array.ptr;
  data = ((char *)h + sizeof(struct ahead));
 }

 /* global color map stuff */
 gcmflag = gh.mask >> 7;	/* top bit in mask */
 if (gcmflag) {
 cr = (gh.mask >> 4) & 0x7;	cr += 1;	cr = 1 << cr;
 pixel = gh.mask & 0x7;	pixel += 1;	pixel = 1 << pixel;
 if (vn == 89) gsortflag = (gh.mask >> 3) & 1; else gsortflag = 0;
 /* printf("cr, pixel, gsortflag = %d %d %d\n", cr, pixel, gsortflag); */
 
 /* we don't bother setting the data array to the background color unless
 we find (later) that the image is smaller then the screen size */
 background_color = gh.background;

 /* still in global color table exist conditional, read the color table
 and load into 3rd arg if it exists */
 loadcolortable(fin, pixel, cmsym);
 }
 
 quit = 0;	status = 1;
 /*  read the next separator and try to process */
 
 bad = 0;
 do {
  sep = getc(fin);
  /* printf("separator %#x\n", sep); */
  switch (sep) {
   case EOF: perror("gifread, separator");
 	quit = 1;
   	status = -1;
   	break;
   case 0x3b:
	/* must be the end */
	//printf("end of image\n");
	quit = 1;
	break;
   case 0x21:
	/* an extension of some sort */
	readextension(fin);	/* currently nothing is done */
	break;
   case 0x2c:
	/* normal, read image descriptor and image */
	nim++;
	if (memloadflag) {
	 /* check if enough pointers */
	 if (nim > max_count) {
 	  max_count += 64;
	  if ( !(gifset->images = realloc(gifset->images,max_count*sizeof(struct ImageArray))))
  	   { printf("malloc failure 1 in gifmemload\n");
 	     fclose(fin); return -1; }
 	   }
 	   /* now load the compressed image in memory */
	   gifset->count = nim;
	   stat = loadimage(fin, cmsym, &(gifset->images[nim-1]), scratch);
	   if (stat == -1) { gif_free(gifset); quit = 1; status = stat; }
	} else {
	/* allow for 3-D cubes, have to extend the data array */
	 if (nim > 1) {
	  dim[2] = nim;
	  if (extend_array(result_sym, 3, dim) != 1) {
	 	 printf("3-D GIF file, malloc failure, too large?\n");
	 	 fclose(fin); return -1; }
	  h = (struct ahead *) sym[result_sym].spec.array.ptr;
	  data = ((char *)h + sizeof(struct ahead));
	  data = data + (nim - 1)*nxs*nys;  /* assumes byte array */
	  }
	  readimage(fin, cmsym, data);
 	}
 	bad = 0;	/* reset the bad counter */
 	break;
   default:
	printf("illegal GIF block type %#x\n", sep);
	/* the terminators used to be here, hence the bad count. Keep
	for a while though we should really just bug out when this happens,
	instead we read on for a while hoping to find something with
	meaning. */
   case 0:
 	/* usually a terminator not handled, the image decoder always
 	leaves an extra one it seems. OK if not too many in a row. So
 	handle the same as bad but without the message. */
 	break;
 	bad++;
 	if (bad > 256) {
 	 quit = 1;
   	 status = -1;
   	 }
	break;
 }
 } while (!quit);

 fclose(fin);
 if (nim > 1) printf("GIF file was mulit-image, number = %d\n", nim);
 if (memloadflag) free(scratch);
 return status;		/* normally a 1 */
 }
 /*------------------------------------------------------------------------- */
void readextension(fin)
 /* Read a GIF extension block (and do nothing with it). */
 FILE	*fin;
 {
 unsigned char code,count;
 char buf[256];
 int	i;
 code = getc(fin);
 //printf("note , extension with code = %#x\n", code);
 if (code == 0xff) {
      //printf("application extension\n");
      count = getc(fin);
      //printf("count = %d\n", count);
      fread(buf, 1, count, fin);
      buf[count] = NULL;
      //printf("application ID is: %s\n", buf);
      if (count >= 8) {
      /* check for NETSCAPE2.0 animated gif, would normally have 11 */
      if (strncmp(buf,"NETSCAPE",8) == 0) {
      printf("NETSCAPE extension, only known is animated GIF\n");
      count = getc(fin);
      fread(buf, 1, count, fin);
      gif_loop = buf[1] + 256*buf[2];
      printf("gif_loop = %d\n", gif_loop);
      }}
      while (count = getc(fin)) 
	{
	fread(buf, 1, count, fin);
	printf("count = %d\n", count);
	i = 0;
	if (count) {
      	  printf("next %d bytes ", count);
      	  while (count--) printf("%d ", (byte) buf[i++]);
      	  printf("\n");
      	  }
	}
 } else {
 switch (code) {
   case 0xf9:
      //printf("graphics control extension\n");
      // decode and show the parameters
      while (count = getc(fin)) 
	{
	fread(buf, 1, count, fin);
	if (count != 4) printf("ILLEGAL blocksize = %n\n", count);
	i = buf[0];
	//printf("packed is %#x\n", i);
	//printf("transparent color flag = %d\n", i & 1);
	//printf("user input flag        = %d\n", (i >> 1) & 1);
	//printf("disposal flag          = %d\n", (i >> 2) & 15);
	i = buf[1] + 256*buf[2];
	//printf("delay time = %d\n", i);
	//printf("transparent color index = %d\n", buf[3]);
	}
      return;
   case 0x01:
      printf("plain text extension\n");
      break;
   case 0xfe:
      printf("comment extension\n");
      break;
   default:
      printf("unknown extension\n");
      break;
  }
 /* read the whole extension, depends on a zero terminator */
 /* also reads over any text sub-blocks */
 while (count = getc(fin)) fread(buf, 1, count, fin);
 }
 }
 /*------------------------------------------------------------------------- */
void readimage(fin, cmsym, data)
 FILE	*fin;
 int	cmsym;
 char	*data;
 {
 /* 9/7/99 - some mods for 3-D cubes */
 struct GIFImage gimage;
 int	nx, ny, ix, iy, local, interleaved, localbits, nc, fflag;
 int	n, m, stride, interleave;
 char	*image, *p, *p2;
 if (fread(&gimage.left_lsb,1,9,fin) != 9) {
 perror("gifread in image descriptor");
 quit = 1;	status = -1;	return; }
 /* get offsets, etc */
 ix = ( (gimage.left_msb << 8) | gimage.left_lsb );
 iy = ( (gimage.top_msb << 8) | gimage.top_lsb );
 nx = ( (gimage.width_msb << 8) | gimage.width_lsb );
 ny = ( (gimage.height_msb << 8) | gimage.height_lsb );
 local = gimage.mask & 0x80;
 interleave = gimage.mask & 0x40;
 /* printf("ix, iy, nx, ny = %d %d %d %d\n", ix, iy, nx, ny); */
 /* printf("mask = %#x\n", gimage.mask); */
 if (local) {
 /* a local color table, not too common, we just replace the global if
 there was one */
 localbits = (gimage.mask & 0x7) + 1;
 nc = 1 << localbits;
 //if (gcmflag) printf("global color table superseded in GIF file\n");
 loadcolortable(fin, nc, cmsym);
 }
 /* normally the image is the same size as screen, if not we fill the unused
 part of the screen with the background color, if there are multiple
 images we now put them in a cube with each plane of the screen size
 and each image the same or smaller */
 
 if (ix || iy) fflag = 0; else { if (nx != nxs || ny != nys) fflag = 0;
 	else fflag =1; }
 if (fflag) image = data; else { image = malloc(nx * ny);
	if (!image) { printf("malloc error in GIFREAD\n"); quit=1; status=-1;
   	return; }
 }
 /* now read in */
 readraster(nx * ny, fin, image); if (status != 1) { quit = 1; return; }
 /* handle interleaf/interlace, make a note of it */
 /* if not the screen size, load into screen image and free temp */
 /* printf("fflag = %d\n", fflag); */
 if (!fflag) {
 /* first load the screen with background, the whole thing so not as
 efficient as it could be but this doesn't happen often */
 p = data; n = nxs*nys; while (n--) *p++ = background_color;
 p2 = image;	p = data + ix + iy*nxs;
 m = ny;	stride = nxs - nx;
 while (m--) { n = nx;  while (n--) {*p++ = *p2++; }  p += stride; }
 free(image);
 }
 }
 /*------------------------------------------------------------------------- */
int loadimage(fin, cmsym, image, scratch)
 /* does not decompress, just moves the compressed data to memory */
 FILE	*fin;
 int	cmsym;
 struct SingleImage	*image;
 char	*scratch;
 {
 /* 9/7/99 - some mods for 3-D cubes */
 struct GIFImage gimage;
 int	local, localbits, nc, fflag, size, maxsize, count;
 char	*p, *p2;
 unsigned char buf[255];

 if (fread(&gimage.left_lsb,1,9,fin) != 9) {
  perror("gifread in image descriptor");
  quit = 1;	status = -1;	return; }
 /* get offsets, etc */
 image->ix = ( (gimage.left_msb << 8)   | gimage.left_lsb );
 image->iy = ( (gimage.top_msb << 8)    | gimage.top_lsb );
 image->nx = ( (gimage.width_msb << 8)  | gimage.width_lsb );
 image->ny = ( (gimage.height_msb << 8) | gimage.height_lsb );
 local = gimage.mask & 0x80;
 image->interleave = gimage.mask & 0x40;
 /* printf("mask = %#x\n", gimage.mask); */
 if (local) {
 /* a local color table, not too common, we just replace the global if
 there was one */
 localbits = (gimage.mask & 0x7) + 1;
 nc = 1 << localbits;
 //if (gcmflag) printf("global color table superseded in GIF file\n");
 loadcolortable(fin, nc, cmsym);
 }
 
 /* now read in to get the size and then transfer */
 size = 1;
 maxsize = nxs*nys;
 *scratch = getc(fin);
 printf("*scratch = %d\n", *scratch);
 /* note that we don't load the gif packet counts */
 for (count = getc(fin); count > 0; count = getc(fin)) {
            if (size > maxsize) {
             /* bad problem, shouldn't happen */
             printf("gif read problem, compressed size exceeds limit\n");
             return -1; }
            fread(scratch+size,1,count,fin);  size = size + count; }
 image->bsize = size;
 /* now allocate only what we need and transfer */
 image->pointer = malloc(size);
 if (!(image->pointer)) {
 	printf("gif memload, malloc failure for pointers\n");
 	return -1; }
 /* copy over */
 memcpy(image->pointer, scratch, size);
 return 1;
 }
 /*------------------------------------------------------------------------- */
void loadcolortable(fin, nc, cmsym)
 FILE	*fin;
 int	nc, cmsym;
 {
 int	dim[8], ncolmap;
 struct ahead   *h;
 char	*colormap;
 /* we either load the color table into ana symbol cmsym or we just
 skip over the area in the file */
 ncolmap = 3*nc;
 if (cmsym) {
 dim[0] = 3;	dim[1] = nc;
 if ( redef_array(cmsym, 0, 2, dim) != 1) { status = -1;  quit = 1; return; }
 h = (struct ahead *) sym[cmsym].spec.array.ptr;
 colormap = ((char *)h + sizeof(struct ahead));
 if (fread(colormap, 1, ncolmap,fin) != ncolmap)
 	{ perror("gifread, colormap"); status = -1;  quit = 1; return; }
 } else if (fseek(fin, ncolmap, SEEK_CUR) == -1)
 	{ perror("gifread, colormap"); status = -1;  quit = 1; return; }
 return;
 }
 /*------------------------------------------------------------------------- */
void fatal(s)
 /* well, not really, in ANA we like to stick around */
 char *s;
 {
        fprintf(stderr,"giftops: %s\n",s);
        quit = 1;	status = -1;
 }
 /*------------------------------------------------------------------------- */
 /* Output the bytes associated with a code to the raster array */

void outcode(p,fill)
 register codetype *p;
 register unsigned char **fill;
 {
        if (p->prefix) outcode(p->prefix,fill);
        *(*fill)++ = p->suffix;
 }

 /* Process a compression code.  "clear" resets the code table.  Otherwise
   make a new code table entry, and output the bytes associated with the
   code. */

 /*------------------------------------------------------------------------- */
void process(code,fill)
 register code;
 unsigned char **fill;
 {
        static avail,oldcode;
        register codetype *p;

        if (code == clear) {
            codesize = datasize + 1;
            codemask = (1 << codesize) - 1;
            avail = clear + 2;
            oldcode = -1;
        } else if (code < avail) {
            outcode(&codetable[code],fill);
            if (oldcode != -1) {
                p = &codetable[avail++];
                p->prefix = &codetable[oldcode];
                p->first = p->prefix->first;
                p->suffix = codetable[code].first;
                if ((avail & codemask) == 0 && avail < 4096) {
                    codesize++;
                    codemask += avail;
                }
            }
            oldcode = code;
        } else if (code == avail && oldcode != -1) {
            p = &codetable[avail++];
            p->prefix = &codetable[oldcode];
            p->first = p->prefix->first;
            p->suffix = p->first;
            outcode(p,fill);
            if ((avail & codemask) == 0 && avail < 4096) {
                codesize++;
                codemask += avail;
            }
            oldcode = code;
        } else {
            fatal("illegal code in raster data");
        }
}
 /*------------------------------------------------------------------------- */
void readraster(nsize, fin, raster)
 int nsize;
 FILE	*fin;
 unsigned char	*raster;
 {
	unsigned char *fill;
        unsigned char buf[255];
        register bits=0;
        register unsigned count,datum=0;
        register unsigned char *ch;
        register int code;

	fill = raster;
        datasize = getc(fin);
        //printf("datasize = %d\n", datasize);
        clear = 1 << datasize;
        eoi = clear+1;
        codesize = datasize + 1;
        codemask = (1 << codesize) - 1;
        codetable = (codetype*) malloc(4096*sizeof(codetype));
        if (!codetable) fatal("not enough memory for code table");
        for (code = 0; code < clear; code++) {
            codetable[code].prefix = (codetype*)0;
            codetable[code].first = code;
            codetable[code].suffix = code;
        }
        for (count = getc(fin); count > 0; count = getc(fin)) {
            fread(buf,1,count,fin);
            for (ch=buf; count-- > 0; ch++) {
                datum += *ch << bits;
                bits += 8;
                while (bits >= codesize) {
                    code = datum & codemask;
                    datum >>= codesize;
                    bits -= codesize;
                    if (code == eoi) goto exitloop;  /* This kludge put in
                                                        because some GIF files
                                                        aren't standard */
		    process(code,&fill);
                }
            }
        }
 exitloop:
 //printf("last count in readraster = %d\n", count);
 if (count) { count = getc(fin); printf("extra block, count = %d\n", count); }
        if (fill != raster +nsize) fatal("raster has the wrong size");
        free(codetable);
 }
 /*------------------------------------------------------------------------- */
void loadraster(nsize, buf, raster, bsize)
 /* very similar to readraster but uses data already in memory */
 int nsize, bsize;
 unsigned char	*raster, *buf;
 {
	unsigned char *fill;
        register bits=0;
        register unsigned count,datum=0;
        register unsigned char *ch;
        register int code;

	fill = raster;
        datasize = *buf++;
        clear = 1 << datasize;
        eoi = clear+1;
        codesize = datasize + 1;
        codemask = (1 << codesize) - 1;
        codetable = (codetype*) malloc(4096*sizeof(codetype));
        if (!codetable) fatal("not enough memory for code table");
        for (code = 0; code < clear; code++) {
            codetable[code].prefix = (codetype*)0;
            codetable[code].first = code;
            codetable[code].suffix = code;
        }
        count = bsize - 1;	/* took one already above */
        for (ch=buf; count-- > 0; ch++) {
                datum += *ch << bits;
                bits += 8;
                while (bits >= codesize) {
                    code = datum & codemask;
                    datum >>= codesize;
                    bits -= codesize;
                    if (code == eoi) goto exitloop;  /* This kludge put in
                                                        because some GIF files
                                                        aren't standard */
		    process(code,&fill);
                }
            }
 exitloop:
 if (fill != raster +nsize) fatal("raster has the wrong size");
 free(codetable);
 }
 /*------------------------------------------------------------------------- */
