/*
 * $Id: readgif.cxx,v 4.1 2004/05/25 06:40:20 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

// readgif.C

// adapted for my own purposed by Hugues Talbot	21 Jan 1998
// in particular got rid of the silly conversion to pixmap!
// now returns the data in a simple GL or RGB buffer

// Extensively modified from original code for gif2ras by
// Patrick J. Naughton of Sun Microsystems.  The original
// copyright notice follows:

/* gif2ras.c - Converts from a Compuserve GIF (tm) image to a Sun Raster image.
 *
 * Copyright (c) 1988 by Patrick J. Naughton
 *
 * Author: Patrick J. Naughton
 * naughton@wind.sun.com
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 */
#include "imnmspc.hxx" // name space definition if needed 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "imview.hxx"
#include "imSystem.hxx"
#include "imageIO.hxx"
#include "readgif.hxx"

extern imageIO *IOBlackBox;

// DEC cxx doesn't like that!
static uchar  *loadGifImage(FILE *GifFile, int *imWidth,int *imHeight, int *imDepth, int inumber=0);


int readGifImage(const char *GifFileName,	// file to read
		 int inumber)		// which image in movie (0 = first)
{
    FILE *fp; 
    uchar *outbuf;
    int    imWidth, imHeight, imDepth;
    
    if ((fp = im_fopen(GifFileName, "rb")) == 0) {
	errprintf("Cannot open %s\n", GifFileName);
	return 0;
    }

    outbuf = loadGifImage(fp, &imWidth, &imHeight, &imDepth, inumber);

    // this will require no translation
    IOBlackBox->setImData(outbuf);
    IOBlackBox->setCurrImgWidth(imWidth);
    IOBlackBox->setCurrImgHeight(imHeight);
    IOBlackBox->setCurrImgThickness(1);
    IOBlackBox->setXOffset(0);
    IOBlackBox->setYOffset(0);
    IOBlackBox->setZOffset(0);
    IOBlackBox->setCurrImgNbComps(1); // only a single image in a GIF file
    IOBlackBox->setCurrImgNbSamples(imDepth);
    IOBlackBox->setCurrImgSpp(imDepth); // needed here because no currBuffp
    IOBlackBox->setCurrPixType(IM_UINT1);
    IOBlackBox->setCurrImgType((imDepth == 3) ? IM_RGB : IM_SPECTRUM);
    IOBlackBox->setImgDesc("GIF");
    
    fclose(fp);

    return (outbuf == 0);
}


#define NEXTBYTE getc(GifFile)
#define GETSHORT(var) var = NEXTBYTE; var += NEXTBYTE << 8

// This function returns the number of frames
// in an animated GIF file. Negative numbers indicate
// errors
int gifnbsubfiles(const char *fname)
{
    int   i;
    FILE *GifFile;
    int   inumber = 0;
    
    if ((GifFile = im_fopen(fname, "rb")) == NULL)
	return -1; // can't read the file
    
    {
	char b[6];
	if (fread(b,1,6,GifFile)<6) return 0; /* quit on eof */
	if (b[0]!='G' || b[1]!='I' || b[2] != 'F') {
	    dbgprintf("not a GIF file!\n"); return 0;}
	if (b[3]!='8' || b[4]>'9' || b[5]!= 'a')
	    dbgprintf("GIF version %c%c%c. May cause trouble\n",b[3],b[4],b[5]);
    }

    int Width; GETSHORT(Width);
    int Height; GETSHORT(Height);
    
    uchar ch = NEXTBYTE;
    char HasColormap = ((ch & 0x80) != 0);
    int BitsPerPixel = (ch & 7) + 1;
    int ColorMapSize = 1 << BitsPerPixel;
    // int OriginalResolution = ((ch>>4)&7)+1;
    // int SortedTable = (ch&8)!=0;
    NEXTBYTE; // Background Color index
    NEXTBYTE; // Aspect ratio is N/64

    // Read in global colormap:
    uchar transparent_pixel = 0;
    char has_transparent = 0;
    if (HasColormap) {
	for (i=0; i < ColorMapSize; i++) {
	    NEXTBYTE; NEXTBYTE; NEXTBYTE;
	}
    } else {
	dbgprintf("File does not have a colormap.\n");
    }

    int CodeSize;		/* Code size, init from GIF header, increases... */
    char Interlace;

    for (;;) {

	i = NEXTBYTE;
	if (i<0) {dbgprintf("reached EOF\n"); break;}
	int blocklen;

	if (i == 0x21) {		// a "gif extension"

	    ch = NEXTBYTE;
	    blocklen = NEXTBYTE;

	    if (ch==0xF9 && blocklen==4) { // Netscape animation extension

		char bits;
		bits = NEXTBYTE;
		NEXTBYTE; NEXTBYTE; // GETSHORT(delay);
		transparent_pixel = NEXTBYTE;
		if (bits & 1) has_transparent = 1;
		blocklen = NEXTBYTE;

	    } else if (ch == 0xFF) {
		; // Netscape repeat count

	    } else {
		dbgprintf("unknown gif extension 0x%02x\n", ch);

	    }

	} else if (i == 0x2c) {	// an image

	    NEXTBYTE; NEXTBYTE; // GETSHORT(x_position);
	    NEXTBYTE; NEXTBYTE; // GETSHORT(y_position);
	    GETSHORT(Width);
	    GETSHORT(Height);
	    ch = NEXTBYTE;
	    Interlace = ((ch & 0x40) != 0);
	    if (ch&0x80) { 
		// skip over local color map
		int n = 1<<((ch&7)+1); // does this replace ColorMapSize ??
		for (i=0; i < n; i++) {	
		    NEXTBYTE; NEXTBYTE; NEXTBYTE;
		}
	    }
	    CodeSize = NEXTBYTE+1;

	    inumber++;  // count the next block
	    blocklen = NEXTBYTE;
      
	} else {
	    dbgprintf("unknown gif code 0x%02x, piking out.\n", i);
	    blocklen = 0;
	    break; // taking no chance there.
	}

	// skip the data:
	while (blocklen>0) {while (blocklen--) {NEXTBYTE;} blocklen=NEXTBYTE;}
    }

    fclose(GifFile);

    return inumber;
}

static uchar  *loadGifImage(FILE *GifFile,	// file to read
			     int *imWidth,
			     int *imHeight,
			     int *imDepth,
			     int inumber		// which image in movie (0 = first)
    )
{
    uchar *outbuf = 0;
    int i;
    
    

    {
	char b[6];
	if (fread(b,1,6,GifFile)<6) return 0; /* quit on eof */
	if (b[0]!='G' || b[1]!='I' || b[2] != 'F') {
	    dbgprintf("not a GIF file!\n"); return 0;}
	if (b[3]!='8' || b[4]>'9' || b[5]!= 'a')
	    dbgprintf("GIF version %c%c%c. May cause trouble\n",b[3],b[4],b[5]);
    }

    int Width; GETSHORT(Width);
    int Height; GETSHORT(Height);
    
    uchar ch = NEXTBYTE;
    char HasColormap = ((ch & 0x80) != 0);
    int BitsPerPixel = (ch & 7) + 1;
    int ColorMapSize = 1 << BitsPerPixel;
    // int OriginalResolution = ((ch>>4)&7)+1;
    // int SortedTable = (ch&8)!=0;
    NEXTBYTE; // Background Color index
    NEXTBYTE; // Aspect ratio is N/64

    // Read in global colormap:
    uchar transparent_pixel = 0;
    char has_transparent = 0;
    uchar Red[256], Green[256], Blue[256]; /* color map */
    if (HasColormap) {
	for (i=0; i < ColorMapSize; i++) {	
	    Red[i] = NEXTBYTE;
	    Green[i] = NEXTBYTE;
	    Blue[i] = NEXTBYTE;
	}
    } else {
	dbgprintf("File does not have a colormap.\n");
	for (i = 0; i < ColorMapSize; i++)
	    Red[i] = Green[i] = Blue[i] = (i*256+ColorMapSize-1)/ColorMapSize;
    }

    int CodeSize;		/* Code size, init from GIF header, increases... */
    char Interlace;

    for (;;) {

	i = NEXTBYTE;
	if (i<0) {dbgprintf("unexpected EOF\n"); return 0;}
	int blocklen;

	//  if (i == 0x3B) return 0;  eof code

	if (i == 0x21) {		// a "gif extension"

	    ch = NEXTBYTE;
	    blocklen = NEXTBYTE;

	    if (ch==0xF9 && blocklen==4) { // Netscape animation extension

		char bits;
		bits = NEXTBYTE;
		NEXTBYTE; NEXTBYTE; // GETSHORT(delay);
		transparent_pixel = NEXTBYTE;
		if (bits & 1) has_transparent = 1;
		blocklen = NEXTBYTE;

	    } else if (ch == 0xFF) {
		; // Netscape repeat count

	    } else {
		dbgprintf("unknown gif extension 0x%02x\n", ch);

	    }

	} else if (i == 0x2c) {	// an image

	    NEXTBYTE; NEXTBYTE; // GETSHORT(x_position);
	    NEXTBYTE; NEXTBYTE; // GETSHORT(y_position);
	    GETSHORT(Width);
	    GETSHORT(Height);
	    ch = NEXTBYTE;
	    Interlace = ((ch & 0x40) != 0);
	    if (ch&0x80) { 
		// read local color map
		int n = 1<<((ch&7)+1); // does this replace ColorMapSize ??
		for (i=0; i < n; i++) {	
		    Red[i] = NEXTBYTE;
		    Green[i] = NEXTBYTE;
		    Blue[i] = NEXTBYTE;
		}
	    }
	    CodeSize = NEXTBYTE+1;

	    if (!inumber--) break; // okay, this is the image we want
	    blocklen = NEXTBYTE;
      
	} else {
	    dbgprintf("unknown gif code 0x%02x\n", i);
	    blocklen = 0;
	}

	// skip the data:
	while (blocklen>0) {while (blocklen--) {NEXTBYTE;} blocklen=NEXTBYTE;}
    }

    uchar *Image = new uchar[Width*Height];
    if (!Image) {
	errprintf("Insufficient memory\n");
	return(outbuf);
    }
    int YC = 0, Pass = 0; /* Used to de-interlace the picture */
    uchar *p = Image;
    uchar *eol = p+Width;

    int InitCodeSize = CodeSize;
    int ClearCode = (1 << (CodeSize-1));
    int EOFCode = ClearCode + 1;
    int FirstFree = ClearCode + 2;
    int FinChar = 0;
    int ReadMask = (1<<CodeSize) - 1;
    int FreeCode = FirstFree;
    int OldCode = ClearCode;

    // tables used by LZW decompresser:
    short int Prefix[4096];
    uchar Suffix[4096];

    int blocklen = NEXTBYTE;
    uchar thisbyte = NEXTBYTE; blocklen--;
    int frombit = 0;

    for (;;) {

/* Fetch the next code from the raster data stream.  The codes can be
 * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to
 * maintain our location as a pointer and a bit offset.
 * In addition, gif adds totally useless and annoying block counts
 * that must be correctly skipped over. */
	int CurCode = thisbyte;
	if (frombit+CodeSize > 7) {
	    if (blocklen <= 0) {
		blocklen = NEXTBYTE;
		if (blocklen <= 0) break;
	    }
	    thisbyte = NEXTBYTE; blocklen--;
	    CurCode |= thisbyte<<8;
	}
	if (frombit+CodeSize > 15) {
	    if (blocklen <= 0) {
		blocklen = NEXTBYTE;
		if (blocklen <= 0) break;
	    }
	    thisbyte = NEXTBYTE; blocklen--;
	    CurCode |= thisbyte<<16;
	}
	CurCode = (CurCode>>frombit)&ReadMask;
	frombit = (frombit+CodeSize)%8;

	if (CurCode == ClearCode) {
	    CodeSize = InitCodeSize;
	    ReadMask = (1<<CodeSize) - 1;
	    FreeCode = FirstFree;
	    OldCode = ClearCode;
	    continue;
	}

	if (CurCode == EOFCode) break;

	uchar OutCode[1025]; // temporary array for reversing codes
	uchar *tp = OutCode;
	if (CurCode < FreeCode) i = CurCode;
	else if (CurCode == FreeCode) {*tp++ = FinChar; i = OldCode;}
	else {dbgprintf("LZW Barf!\n"); break;}

	while (i >= ColorMapSize) {*tp++ = Suffix[i]; i = Prefix[i];}
	*tp++ = FinChar = i;
	while (tp > OutCode) {
	    *p++ = *--tp;
	    if (p >= eol) {
		if (!Interlace) YC++;
		else switch (Pass) {
		  case 0: YC += 8; if (YC >= Height) {Pass++; YC = 4;} break;
		  case 1: YC += 8; if (YC >= Height) {Pass++; YC = 2;} break;
		  case 2: YC += 4; if (YC >= Height) {Pass++; YC = 1;} break;
		  case 3: YC += 2; break;
		}
		if (YC>=Height) YC=0; /* cheap bug fix when excess data */
		p = Image + YC*Width;
		eol = p+Width;
	    }
	}

	if (OldCode != ClearCode) {
	    Prefix[FreeCode] = OldCode;
	    Suffix[FreeCode] = FinChar;
	    FreeCode++;
	    if (FreeCode > ReadMask) {
		if (CodeSize < 12) {
		    CodeSize++;
		    ReadMask = (1 << CodeSize) - 1;
		}
		else FreeCode--;
	    }
	}
	OldCode = CurCode;
    }

    // We are done reading the file, now convert to xpm:



    
    // transparent pixel must be zero, swap if it isn't:
    if (has_transparent && transparent_pixel != 0) {
	// swap transparent pixel with zero
	p = Image+Width*Height;
	while (p-- > Image) {
	    if (*p==transparent_pixel) *p = 0;
	    else if (!*p) *p = transparent_pixel;
	}
	uchar t;
	t = Red[0]; Red[0] = Red[transparent_pixel]; Red[transparent_pixel] = t;
	t =Green[0];Green[0]=Green[transparent_pixel];Green[transparent_pixel]=t;
	t =Blue[0];Blue[0] =Blue[transparent_pixel];Blue[transparent_pixel] = t;
    }

    // is the image colour or Grey-level?
    *imDepth = 1;
    for (i = 0 ; i < ColorMapSize ; i++) {
	if ((Red[i] != Green[i]) || (Red[i] != Blue[i])) {
	    *imDepth = 3;
	    break;
	}
    }

    *imWidth = Width;
    *imHeight = Height;

    int nbBytes = (*imWidth)*(*imHeight)*(*imDepth);
    outbuf = new uchar[nbBytes];

    if (outbuf != 0) {
	uchar *end;
	uchar *q = Image;

	p = outbuf;
	end = p + nbBytes;
	// interprets the colour LUT
	if (*imDepth == 1) {
	    while (p != end) {
		*p++ = Red[*q++];
	    }
	} else {
	    while (p != end) {
		*p++ = Red[*q];
		*p++ = Green[*q];
		*p++ = Blue[*q++];
	    }
	}
    }

    delete[] Image;
    return outbuf;
}

