/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Jan Wielemaker and Anjo Anjewierden
    E-mail:        jan@swi.psy.uva.nl
    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
    Copyright (c)  1997-2013, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This file reads a .GIF image file. It is used by giftoxpm.c to create an
XpmImage  structure  from  a  .GIF  file.    After   that,  we  can  use
XpmCreateImagefromImage()  to  do  the  tricky  things  such  as  colour
allocation.  This route is probably a little slower than the direct one,
but acceptable and a lot less code.

The code in this file  is   based  on gifread.cpp by chrisdl@pagesz.net,
from  whom  I  cannot  find  his  real   name.  He  based  his  code  on
``Programming for Graphics Files'' by John Levine.  I'm grateful for his
contribution.

http://www.pagesz.net/~chrisdl/software/jpegfile.htm

This reads GIF 87a and 89a.

I converted the code to plain ANSI-C   and changed various Windows idiom
to standard C for easier integration in  XPCE and modified the interface
to reuse the Xpm package to do the tricky work.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include <h/kernel.h>
#include <fcntl.h>
#include "gif.h"

#define INTERLACE	0x40
#define LOCALCOLORMAP   0x80
#define BitSet(byte,bit) (((byte) & (bit))==(bit))
#define ReadOK(file,buffer,len) (Sfread(buffer,1,(len),file)==(len))
#define LM_to_uint(a,b)	(((b)<<8)|(a))

typedef short int		code_int;	/* was int */
typedef long int		count_int;
typedef unsigned char pixval;

static int
ReadColorMap(IOSTREAM *fd, int number,
	     GIFAllocColorTable at, GIFAllocColor ac, void *closure);
static int DoExtension(IOSTREAM *fd, int label,
		       GIFDoExtension func, void *closure);
static int GetDataBlock(IOSTREAM *fd, UCHAR *buf);
static int GetCode (IOSTREAM *fd, int code_size, int flag);
static int LZWReadByte (IOSTREAM *fd,int flag, int  input_code_size);

static int ReadImage(IOSTREAM *fd,
		     PIXEL *bigMemBuf,
		     int width, int height,
		     int ncolors,
		     int interlace);


struct
{ unsigned int Width;
  unsigned int Height;
  unsigned int BitPixel;
  unsigned int ColorResolution;
  unsigned int BackGround;
  unsigned int AspectRatio;
} GifScreen;

struct
{ int transparent;
  int delayTime;
  int inputFlag;
  int disposal;
} Gif89 = { -1, -1, -1, 0 };

char *GIFErrorText;

const char *
GIFError()
{ return GIFErrorText ? GIFErrorText : "No Error";
}

static void
setGifError(const char *fmt)
{ if ( GIFErrorText )
    pceFree(GIFErrorText);

  if ( (GIFErrorText = pceMalloc(strlen(fmt)+1)) )
    strcpy(GIFErrorText, fmt);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GIFReadFD(IOSTREAM *fd,
	  PIXEL **data, int *width, int *height,
	  GIFAllocColorTable at, GIFAllocColor ac, void *closure)
	Read GIF image from the given IO/stream

*data is allocated using malloc().  It will be freed using Xfree(). See
also ximage.c.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
GIFReadFD(IOSTREAM *fd,
	  PIXEL **data, int *width, int *height,
	  GIFAllocColorTable at, GIFAllocColor ac,
	  GIFDoExtension doext,
	  void *closure)
{
  UCHAR buf[16];
  UCHAR c;
  int useGlobalColormap;
  int bitPixel;
  char version[4];
  int w = 0;
  int h = 0;
  PIXEL *bigBuf;
  int rval;

  Gif89.transparent = -1;
  Gif89.delayTime   = -1;
  Gif89.inputFlag   = -1;
  Gif89.disposal    = 0;

  /* read GIF file header */
  if (!ReadOK(fd, buf, 6))
  { setGifError("Error reading GIF Magic");
    return GIF_INVALID;
  }
  /* need the string "GIF" in the header */
  if (strncmp((char *) buf, "GIF", 3) != 0)
  { setGifError("not a valid .GIF file");
    return GIF_INVALID;
  }
  memcpy(version, buf+3, 3);
  version[3] = '\0';

  /* only handle v 87a and 89a */
  if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0))
  { setGifError("Error, Bad GIF Version number");
    return GIF_INVALID;
  }
  /* screen description */
  if (!ReadOK(fd, buf, 7))
  { setGifError("failed to GIF read screen descriptor. Giving up");
    return GIF_INVALID;
  }
  GifScreen.Width = LM_to_uint((UCHAR) buf[0], (UCHAR) buf[1]);
  GifScreen.Height = LM_to_uint((UCHAR) buf[2], (UCHAR) buf[3]);
  GifScreen.BitPixel = 2 << ((UCHAR) buf[4] & 0x07);
  GifScreen.ColorResolution = ((((UCHAR) buf[4] & 0x70) >> 3) + 1);
  GifScreen.BackGround = (UCHAR) buf[5];	/* background color... */

  GifScreen.AspectRatio = (UCHAR) buf[6];


  /* read colormaps */
  if ( BitSet((UCHAR) buf[4], LOCALCOLORMAP) )
  { if ( (rval=ReadColorMap(fd, GifScreen.BitPixel, at, ac, closure))
								!= GIF_OK )
    { setGifError("Error reading GIF colormap");
      return rval;
    }
  }
  /* non-square pixels, so what?   */
  if ((GifScreen.AspectRatio != 0) && (GifScreen.AspectRatio != 49))
  {
    setGifError("Non-square pixels in GIF image.  Ignoring that fact ...");
  }
  /* there can be multiple images in a GIF file... uh? */
  /* what the hell do we do with multiple images? */
  /* so, we'll be interested in just the first image, cause we're lazy */

  for (;;)
  { long bufsize;

    /* read a byte; */
    if (!ReadOK(fd, &c, 1))
    {
      setGifError("Unexpected EOF in GIF.  Giving up");
      return GIF_INVALID;
    }
    /* image terminator */
    if (c == ';')
    {
    }
    if (c == '!')
    { if (!ReadOK(fd, &c, 1))
      { setGifError("Error on extension read.  Giving up");
	return GIF_INVALID;
      }
      DoExtension(fd, c, doext, closure);
      continue;
    }
    if (c != ',')
    {
      /* Ignoring c */
      continue;
    }
    /* read image header */
    if (!ReadOK(fd, buf, 9))
    {
      setGifError("Error on dimension read.  Giving up");
      return GIF_INVALID;
    }
    useGlobalColormap = !BitSet((UCHAR) buf[8], LOCALCOLORMAP);

    bitPixel = 1 << (((UCHAR) buf[8] & 0x07) + 1);

    /* let's see if we have enough mem to continue? */

    if ((int) buf[5] > 4)
    {
      /*AfxMessageBox("This GIF file claims to be > 2000 bytes wide!",MB_OK | MB_ICONINFORMATION); */
    }
    if ((int) buf[7] > 4)
    {
      /*AfxMessageBox("This GIF file claims to be > 2000 bytes high!",MB_OK | MB_ICONINFORMATION); */
    }
    w = LM_to_uint((UCHAR) buf[4], (UCHAR) buf[5]);
    h = LM_to_uint((UCHAR) buf[6], (UCHAR) buf[7]);

    if ((w < 0) || (h < 0))
    { setGifError("Negative image dimensions!  Giving up");
      return GIF_INVALID;
    }
    bufsize = (long) w *(long) h;
    bigBuf  = (PIXEL *)malloc(bufsize * sizeof(PIXEL));

    if (bigBuf == NULL)
    {
      setGifError("Out of Memory in GIFRead");
      return GIF_NOMEM;
    }
    if (!useGlobalColormap)
    { if ( (rval=ReadColorMap(fd, bitPixel, at, ac, closure)) != GIF_OK )
      {	setGifError("Error reading GIF colormap. Giving up");
	pceFree(bigBuf);
	return rval;
      }
      /*read image */
      if ( (rval=ReadImage(fd, bigBuf, w, h, bitPixel,
			   BitSet((UCHAR) buf[8], INTERLACE))) != GIF_OK )
      { setGifError("Error reading GIF file.  LocalColorMap. Giving up");
	pceFree(bigBuf);
	return rval;
      }
    } else
    { if ( (rval=ReadImage(fd, bigBuf, w, h, GifScreen.BitPixel,
			   BitSet((UCHAR) buf[8], INTERLACE))) != GIF_OK )
      { setGifError("Error reading GIF file.  GIFScreen Colormap.  Giving up");
	pceFree(bigBuf);
	return rval;
      }
    }
    break;
  }

  *width = w;
  *height = h;
  *data = bigBuf;

  return GIF_OK;
}


static int
ReadColorMap(IOSTREAM *fd, int number,
	     GIFAllocColorTable at, GIFAllocColor ac, void *closure)
{ int i;
  UCHAR rgb[3];
  int rval;

  if ( (rval=(*at)(number, closure)) != GIF_OK )
    return rval;

  for (i = 0; i < number; ++i)
  { if (!ReadOK(fd, rgb, sizeof(rgb)))
      return GIF_INVALID;

    rval = (*ac)(i, rgb[0], rgb[1], rgb[2], closure);

    if ( rval != GIF_OK )
      return rval;
  }

  return GIF_OK;
}


static int
DoExtension(IOSTREAM * fd, int label, GIFDoExtension doext, void *cl)
{
  static char buf[256];

  switch (label)
  {
  case 0x01:					/* Plain Text Ext */
  case 0xff:					/* Appl ext */
    break;
  case 0xfe:					/* Comment Ext */
    while (GetDataBlock(fd, (UCHAR *) buf) != 0)
    {
      /*AfxMessageBox(buf, MB_OK | MB_ICONINFORMATION); */
    }
    return FALSE;
    break;
  case 0XF9:					/* Graphic Ctrl Ext */
    (void) GetDataBlock(fd, (UCHAR *) buf);
    Gif89.disposal = (buf[0] >> 2) & 0x7;
    Gif89.inputFlag = (buf[0] >> 1) & 0x1;
    Gif89.delayTime = LM_to_uint(buf[1], buf[2]);
    if ((buf[0] & 0x1) != 0)
    { Gif89.transparent = buf[3]&0xff;
      (*doext)(GIFEXT_TRANSPARENT, (void *)(intptr_t)Gif89.transparent, cl);
    }

    while (GetDataBlock(fd, (UCHAR *) buf) != 0) ;
    return FALSE;
    break;
  default:
    sprintf(buf, "UNKNOWN (0x%02x)", label);
    break;
  }

  while (GetDataBlock(fd, (UCHAR *) buf) != 0) ;

  return FALSE;
}

static int ZeroDataBlock = FALSE;

static int
GetDataBlock(IOSTREAM * fd, UCHAR * buf)
{
  UCHAR count;

  if (!ReadOK(fd, &count, 1))
  {
    /*GIFErrorText="Error in GIF DataBlock Size"; */
    return -1;
  }
  ZeroDataBlock = count == 0;

  if ((count != 0) && (!ReadOK(fd, buf, count)))
  {
    /*GIFErrorText="Error reading GIF datablock"; */
    return -1;
  }
  return count;
}

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

  if (flag)
  {
    curbit = 0;
    lastbit = 0;
    done = FALSE;
    return 0;
  }
  if ((curbit + code_size) >= lastbit)
  {
    if (done)
    {
      if (curbit >= lastbit)
      {
	/*GIFErrorText="Ran off the end of my bits"; */
	return 0;
      }
      return -1;
    }
    buf[0] = buf[last_byte - 2];
    buf[1] = buf[last_byte - 1];

    if ((count = GetDataBlock(fd, &buf[2])) == 0)
      done = TRUE;

    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;
}

static int
LZWReadByte(IOSTREAM * 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 unsigned short next[1 << MAX_LZW_BITS];
  static UCHAR vals[1 << MAX_LZW_BITS];
  static UCHAR stack[1 << (MAX_LZW_BITS + 1)];
  static UCHAR *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 = clear_code + 2;
    max_code_size = 2 * clear_code;

    GetCode(fd, 0, TRUE);

    fresh = TRUE;

    for (i = 0; i < clear_code; ++i)
    {
      next[i] = 0;
      vals[i] = i;
    }

    for (; i < (1 << MAX_LZW_BITS); ++i)
      next[i] = vals[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&255);
  }
  if (sp > stack)
    return *--sp;

  while ((code = GetCode(fd, code_size, FALSE)) >= 0)
  {
    if (code == clear_code)
    {
      for (i = 0; i < clear_code; ++i)
      {
	next[i] = 0;
	vals[i] = i;
      }
      for (; i < (1 << MAX_LZW_BITS); ++i)
	next[i] = vals[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&255);
    } else if (code == end_code || code > max_code)
    {
      int count;
      UCHAR buf[260];			/* Block buffer */

      if (ZeroDataBlock)
	return -2;

      while ((count = GetDataBlock(fd, buf)) > 0) ;

      if (count != 0)
	/*AfxMessageBox("Missing EOD in GIF data stream (common occurrence)",MB_OK); */
	return -2;
    }
    incode = code;

    if (code == max_code)
    { if ( sp < stack+sizeof(stack) )	/* stack is UCHAR */
	*sp++ = firstcode;
      code = oldcode;
    }
    while (code >= clear_code && sp < stack+sizeof(stack) )
    {
      *sp++ = vals[code];
      if (code == (int) next[code])
      {
	/*GIFErrorText="Circular table entry, big GIF Error!"; */
	return -1;
      }
      code = next[code];
    }

    if ( sp < stack+sizeof(stack) )
      *sp++ = firstcode = vals[code];

    if ((code = max_code) < (1 << MAX_LZW_BITS))
    {
      next[code] = oldcode;
      vals[code] = firstcode;
      ++max_code;
      if ((max_code >= max_code_size) &&
	  (max_code_size < (1 << MAX_LZW_BITS)))
      {
	max_code_size *= 2;
	++code_size;
      }
    }
    oldcode = incode;

    if (sp > stack)
      return ((*--sp) & 255);
  }
  return (code&255);
}


static int
ReadImage(IOSTREAM *fd,
	  PIXEL *bigMemBuf,
	  int width, int height,
	  int ncolors,
	  int interlace)
{
  UCHAR c;
  int color;
  int xpos = 0, ypos = 0, pass = 0;
  int lines = 0;
  long curidx;

  if ( !ReadOK(fd, &c, 1) || c > MAX_LZW_BITS )
  { return GIF_INVALID;
  }
  if (LZWReadByte(fd, TRUE, c) < 0)
  { return GIF_INVALID;
  }
  while ((color = LZWReadByte(fd, FALSE, c)) >= 0)
  {
    curidx = (long) xpos + (long) ypos *(long) width; /* optimize */

    if ( color >= ncolors )
    { DEBUG(NAME_gif, Cprintf("Color %d; ncolors = %d\n", color, ncolors));
      return GIF_INVALID;
    }
    bigMemBuf[curidx] = color;

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

	if (ypos >= height)
	{
	  ++pass;
	  switch (pass)
	  {
	  case 1:
	    ypos = 4;
	    break;
	  case 2:
	    ypos = 2;
	    break;
	  case 3:
	    ypos = 1;
	    break;
	  default:
	    goto fini;
	  }
	}
      } else
      { ++ypos;
      }
      ++lines;
    }
    if (ypos >= height)
      goto fini;
  }
  DEBUG(NAME_gif, Cprintf("Short file\n"));
  return GIF_INVALID;			/* short file */

fini:
  if ( lines != height )
  { DEBUG(NAME_gif, Cprintf("Lines = %d; height=%d\n", lines, height));
    return GIF_INVALID;
  }

  return GIF_OK;			/* end is 0x3B, but we only read the */
}

