/*                       GIF Load/Save Source for WGT
                                  Free code

Notes:  There are now restrictions on the use of the GIF file format.
        Royalties must be paid to CompuServe if the program you have created
        is sold for profit. Freeware programs may use this code without
        any payments.

        Exact details can be obtained from CompuServe.
*/


#include <stdlib.h>
#include <stdio.h>
#include <mem.h>
#include <dos.h>
#include <malloc.h>
#include <wgt5.h>


#define GOOD_READ       0
#define BAD_FILE        1
#define BAD_READ        2
#define UNEXPECTED_EOF  3
#define BAD_CODE        4
#define BAD_FIRSTCODE   5
#define BAD_ALLOC       6
#define BAD_SYMBOLSIZE  7

#define NO_CODE         -1

#define largest_code    4095
#define table_size      5003


typedef struct {
        signed char sig[6];
        unsigned short screenwidth, screendepth;
        unsigned char flags, background, aspect;
        } GIFHEADER;

typedef struct {
        unsigned short left, top, width, depth;
        unsigned char flags;
        } IMAGEBLOCK;

typedef struct {
        short width, depth, bits;
        short flags;
        short background;
        signed char palette[768];
        short (*setup)();
        short (*closedown)();
        short (*saveline)();
        short (*saveext)();
        } FILEINFO;

typedef struct {
        signed char blocksize;
        signed char flags;
        unsigned short delay;
        signed char transparent_colour;
        signed char terminator;
        } CONTROLBLOCK;

typedef struct {
        signed char blocksize;
        unsigned short left, top;
        unsigned short gridwidth, gridheight;
        signed char cellwidth, cellheight;
        signed char forecolour, backcolour;
        } PLAINTEXT;

typedef struct {
        signed char blocksize;
        signed char applstring[8];
        signed char authentication[3];
        } APPLICATION;

static char code_buffer[259];
static FILEINFO fi;
static block ptr;
static int pixctr;
static int maxctr;
static short oldcode[table_size];
static short currentcode[table_size];
static char newcode[table_size];
static short code_size;
static short clear_code;
static short eof_code;
static short bit_offset;
static short byte_offset;
static short bits_left;
static short max_code;
static short free_code;

void putline (block p, int n)
{
  if (n >= 0 && n < fi.depth)
    memcpy (ptr + ((int)n * (int)fi.width) + 4, p, fi.width);
}


void putextension (FILE *fp)
{
  PLAINTEXT pt;
  CONTROLBLOCK cb;
  APPLICATION ap;
  short c, n, i;

  fread (&c, 1, 1, fp);
  switch (c) {
    case 0x0001 : fread ((char *)&pt, 1, sizeof(PLAINTEXT), fp);
                  do {
                    if ((n = fgetc(fp)) != EOF)
                    {
                      for (i = 0; i < n; i++)
                        fgetc (fp);
                    }
                  } while (n > 0 && n != EOF);
                  break;
    case 0x0009 : fread ((char *)&cb, 1, sizeof(CONTROLBLOCK), fp);
                  break;
    case 0x00fe : do {
                    if ((n = fgetc (fp)) != EOF)
                    {
                      for (i = 0; i < n; i++)
                        fgetc (fp);
                    }
                  } while (n > 0 && n != EOF);
                  break;
    case 0x00ff : fread ((char *)&ap, 1, sizeof(APPLICATION), fp);
                  do {
                    if ((n = fgetc (fp)) != EOF)
                    {
                      for (i = 0; i < n; i++)
                        fgetc (fp);
                    }
                  } while (n > 0 && n != EOF);
                  break;
    default :     if ((n = fgetc (fp)) != EOF)
                  {
                    for (i = 0; i < n; i++)
                      fgetc (fp);
                  }
                  break;
  }
}


short unpackimage (FILE *fp, short bits)
{
  short bits2;
  short codesize;
  short codesize2;
  short nextcode;
  short thiscode;
  short oldtoken;
  short currentcode;
  short oldcode;
  short bitsleft;
  short blocksize;
  short line = 0;
  short byte = 0;
  short pass = 0;

  char *p;
  char *q;
  char b[255];
  char *u;
  char *linebuffer;

  static char firstcodestack[4096];
  static char lastcodestack[4096];
  static short codestack[4096];

  static short wordmasktable[] = { 0x0000, 0x0001, 0x0003, 0x0007,
                                   0x000f, 0x001f, 0x003f, 0x007f,
                                   0x00ff, 0x01ff, 0x03ff, 0x07ff,
                                   0x0fff, 0x1fff, 0x3fff, 0x7fff };

  static short inctable[] = { 8, 8, 4, 2, 0 };
  static short startable[] = { 0, 4, 2, 1, 0 };

  p = q = b;
  bitsleft = 8;

  if (bits < 2 || bits > 8)
    return (BAD_SYMBOLSIZE);
  bits2 = 1 << bits;
  nextcode = bits2 + 2;
  codesize2 = 1 << (codesize = bits + 1);
  oldcode = oldtoken = NO_CODE;

  if ((linebuffer = malloc (fi.width)) == NULL)
    return (BAD_ALLOC);

  /* loop until something breaks */
  for (;;) {
    if (bitsleft == 8) {
      if (++p >= q && (((blocksize = fgetc(fp)) < 1) ||
         (q=(p=b)+fread (b, 1, blocksize, fp)) < (b+blocksize))) {
        free (linebuffer);
        return (UNEXPECTED_EOF);
      }
      bitsleft = 0;
    }
    thiscode = *p;
    if ((currentcode = (codesize+bitsleft)) <= 8) {
      *p >>= codesize;
      bitsleft = currentcode;
    }
    else {
      if (++p >= q && (((blocksize = fgetc(fp)) < 1) ||
         (q=(p=b)+fread (b, 1, blocksize, fp)) < (b+blocksize))) {
        free (linebuffer);
        return (UNEXPECTED_EOF);
      }
      thiscode |= *p << (8 - bitsleft);
      if (currentcode <= 16)
        *p >>= (bitsleft = currentcode - 8);
      else {
        if (++p >= q && (((blocksize = fgetc(fp)) < 1) ||
           (q=(p=b)+fread (b, 1, blocksize, fp)) < (b+blocksize))) {
          free (linebuffer);
          return (UNEXPECTED_EOF);
        }
        thiscode |= *p << (16 - bitsleft);
        *p >>= (bitsleft = currentcode - 16);
      }
    }
    thiscode &= wordmasktable[codesize];
    currentcode = thiscode;

    if (thiscode == (bits2+1))
      break;
    if (thiscode > nextcode) {
      free (linebuffer);
      return (BAD_CODE);
    }

    if (thiscode == bits2) {
      nextcode = bits2 + 2;
      codesize2 = 1 << (codesize = (bits + 1));
      oldtoken = oldcode = NO_CODE;
      continue;
    }

    u = firstcodestack;

    if (thiscode == nextcode) {
      if (oldcode == NO_CODE) {
        free (linebuffer);
        return (BAD_FIRSTCODE);
      }
      *u++ = oldtoken;
      thiscode = oldcode;
    }

    while (thiscode >= bits2) {
      *u++ = lastcodestack [thiscode];
      thiscode = codestack[thiscode];
    }

    oldtoken = thiscode;
    do {
      linebuffer[byte++] = thiscode;
      if (byte >= fi.width) {
        putline (linebuffer, line);
        byte = 0;
        /* check for interlaced image */
        if (fi.flags & 0x40) {
          line += inctable[pass];
          if (line >= fi.depth)
            line = startable[++pass];
        } else ++line;
      }
      if (u <= firstcodestack)
        break;
      thiscode = *--u;
    } while (1);

    if (nextcode < 4096 && oldcode != NO_CODE) {
      codestack[nextcode] = oldcode;
      lastcodestack[nextcode] = oldtoken;
      if (++nextcode >= codesize2 && codesize < 12)
        codesize2 = 1 << ++codesize;
    }
    oldcode = currentcode;
  }
  free (linebuffer);
  return (GOOD_READ);
}


block wloadgif (char *filename, color pal[256])
{
  GIFHEADER gh;
  IMAGEBLOCK iblk;
  int t;
  short b,c;

  if  (wgtlibrary == NULL)
  {
    if  ((libf = fopen (filename, "rb")) == NULL)
      return NULL;
  }
  else
  {
    if  ((libf = fopen (wgtlibrary, "rb")) == NULL)
      return NULL;
    readheader ();
    findfile (filename);
    if  (lresult == 1)
      fseek (libf, lfpos, SEEK_SET);
    if  (checkpassword (password) == 0)
    {
      wsetmode (3);
      printf ("Incorrect password");
      exit (1);
    }
  }
  
  if  ((wgtlibrary != NULL) & (lresult == 0)) goto lblkstop;

  if (fread((char *)&gh, 1, sizeof(GIFHEADER), libf) != sizeof(GIFHEADER) ||
      memcmp(gh.sig, "GIF", 3)) 
  {
    fclose (libf);
    return NULL;
  }

  /* get screen dimensions */
  fi.width = gh.screenwidth;
  fi.depth = gh.screendepth;
  fi.bits = (gh.flags & 0x0007) + 1;
  /* get colour map if there is one */
  if (gh.flags & 0x80) {
    c = 3 * (1 << ((gh.flags & 7) + 1));
    if (fread (pal, 1, c, libf) != c) 
    {
      fclose (libf);
      return NULL;
    }
  }
  /* step through the blocks */
  while ((c=fgetc(libf))==',' || c=='!' || c==0) {
    /* if it's an image block */
    if (c == ',') {
      /* get the start of the image block */
      if (fread (&iblk, 1, sizeof(IMAGEBLOCK), libf) != sizeof(IMAGEBLOCK))
      {
        fclose (libf);
        return NULL;
      }

      /* get the image dimensions */
      fi.width = iblk.width;
      fi.depth = iblk.depth;

      /* get the local colour map if there is one */
      if (iblk.flags & 0x80) {
        b = 3 * (1 << ((iblk.flags & 0x0007) + 1));
        if (fread (pal, 1, b, libf) != c)
        {
          fclose (libf);
          return NULL;
        }
        fi.bits = (iblk.flags & 0x0007) + 1;
      }

      /* get the initial code size */
      if (fread (&c, 1, 1, libf) == 0)
      {
        fclose (libf);
        return NULL;
      }

      fi.flags = iblk.flags;

      if ( (ptr = malloc ( (int)fi.width * (int)fi.depth + 5) ) == NULL)
      {
        fclose (libf);
        return NULL;
      }

      for (b = 0; b < 256; b++)
      {
        pal[b].r >>= 2;
        pal[b].g >>= 2;
        pal[b].b >>= 2;
      }
      *(short *)ptr = fi.width;
      ptr += 2;
      *(short *)ptr = fi.depth;
      ptr -= 2;
      t = unpackimage (libf, c);
      if (t != GOOD_READ)
      {
        free (ptr);
        fclose (libf);
        return NULL;
      }
      else {
        fclose (libf);
        return ptr;
      }
    }
    else if (c == '!')
      putextension (libf);
  };
  lblkstop:
  ;
  fclose (libf);
  return ptr;
}


void init_table (short min_code_size)
{
  short i;

  code_size = min_code_size + 1;
  clear_code = (1<<min_code_size);
  eof_code = clear_code + 1;
  free_code = clear_code + 2;
  max_code = (1 << code_size);

  for (i=0; i<table_size; i++)
    currentcode[i] = 0;
}


void flush (FILE *fp, short n)
{
  fputc (n, fp);
  fwrite (code_buffer, 1, n, fp);
}


void write_code (FILE *fp, short code)
{
  int temp;

  byte_offset = bit_offset >> 3;
  bits_left = bit_offset & 7;

  if (byte_offset >= 254) {
    flush (fp, byte_offset);
    code_buffer[0] = code_buffer[byte_offset];
    bit_offset = bits_left;
    byte_offset = 0;
  }
  if (bits_left > 0) {
    temp = ((int) code << bits_left) | code_buffer[byte_offset];
    code_buffer[byte_offset] = temp;
    code_buffer[byte_offset+1] = (temp >> 8);
    code_buffer[byte_offset+2] = (temp >> 16);
  } else {
    code_buffer[byte_offset] = code;
    code_buffer[byte_offset+1] = (code >> 8);
  }
  bit_offset += code_size;
}


short readpixel (void)
{
  unsigned char pxl;

  pixctr++;
  if (pixctr > maxctr)
    return 9999;
  pxl = *ptr;
  ptr++;
  return (pxl);
}


void compressImage (FILE *fp, unsigned short min_code_size)
{
  short prefix_code;
  short suffix_char;
  short hx, d;

  if (min_code_size < 2 || min_code_size > 9) {
    if (min_code_size == 1)
      min_code_size = 2;
    else return;
  }

  /* write initial code size */
  fputc (min_code_size, fp);

  /* initialize the encoder */
  bit_offset = 0;
  init_table (min_code_size);
  write_code (fp, clear_code);
  if ((suffix_char = readpixel()) == 9999)
    return;

  /* initialize the prefix */
  prefix_code = suffix_char;

  /* get a character to compress */
  while ((suffix_char = readpixel()) != 9999) {
    /* derive an index into the code table */
    hx = (prefix_code ^ (suffix_char << 5)) % table_size;
    d = 1;

    for (;;) {
      /* see if the code is in the table */
      if (currentcode[hx] == 0) {
        /* if not, put it there */
        write_code (fp, prefix_code);
        d = free_code;

        /* find the next free code */
        if (free_code <= largest_code) {
          oldcode[hx] = prefix_code;
          newcode[hx] = suffix_char;
          currentcode[hx] = free_code;
          free_code++;
        }
        
        /* expand the code size or scrap the table */
        if (d == max_code) {
          if (code_size < 12) {
            code_size++;
            max_code <<= 1;
          }
          else {
            write_code (fp, clear_code);
            init_table (min_code_size);
          }
        }
        prefix_code = suffix_char;
        break;
      }
      if (oldcode[hx] == prefix_code &&
          newcode[hx] == suffix_char) 
      {
            prefix_code = currentcode[hx];
            break;
      }
      hx += d;
      d += 2;
      if (hx >= table_size)
        hx -= table_size;
    }
  }

  /* write the prefix code */
  write_code (fp, prefix_code);

  /* and the end of file code */
  write_code (fp, eof_code);

  /* flush the buffer */
  if (bit_offset > 0) 
    flush (fp, (bit_offset+7)/8);

  /* write a zero length block */
  flush (fp, 0);
}


void wsavegif (char *filename, block image, color pal[256])
{
  GIFHEADER gh;
  IMAGEBLOCK iblk;
  short b;
  FILE *fp;
  color p[256];

  if ((fp = fopen (filename, "wb")) == NULL)
    return;

  ptr = image + 4;
  memset ((char *)&gh, 0, sizeof(GIFHEADER));
  memcpy (gh.sig, "GIF87a", 6);
  gh.screenwidth = wgetblockwidth (image);
  gh.screendepth = wgetblockheight (image);
  gh.flags = 0xf7;
  fwrite((char *)&gh, 1, sizeof(GIFHEADER), fp);
  
  for (b = 0; b < 256; b++)
  {
    p[b].r = pal[b].r << 2;
    p[b].g = pal[b].g << 2;
    p[b].b = pal[b].b << 2;
  }
  fwrite (p, 1, 768, fp);
  
  memset ((char *)&iblk, 0, sizeof (IMAGEBLOCK));
  fputc(',', fp);
  iblk.left = 0;
  iblk.top = 0;
  iblk.width = gh.screenwidth;
  iblk.depth = gh.screendepth;
  iblk.flags = 0x7;
  fwrite ((char *)&iblk, 1, sizeof (IMAGEBLOCK), fp);

  pixctr = 0;
  maxctr = iblk.width * iblk.depth;
  compressImage (fp, 8); 
    
  fputc (';', fp);
  fclose (fp);
}
