 /* ************************************************************************ 
 *         The Amulet User Interface Development Environment              *
 * ************************************************************************
 * This code was written as part of the Amulet project at                 *
 * Carnegie Mellon University, and has been placed in the public          *
 * domain.  If you are using this code or any part of Amulet,             *
 * please contact amulet@cs.cmu.edu to be put on the mailing list.        *
 * ************************************************************************/

// Implementation of machine independant GIF loader.
#include <fstream.h>
#include <string.h>
#if __MWERKS__
#include <iostream>
#endif
#include "gem_GIF.h"

// Load a GIF color map from a stream into an image.  
// Return 1/0 success value
int Am_GIF_Image::load_gif_colormap(ifstream &ifs, int flags)
{
  //  iminfo.depth = CTSIZE(flags) + 1; set in caller
  //  iminfo.num_colors = 1 << iminfo.depth;
  iminfo.num_colors = 1 << (CTSIZE(flags) + 1);
  // if we have a color map, delete it before reallocating
  if (color_map != NULL) delete[] color_map;
  color_map = new Am_RGB_Value[iminfo.num_colors];
  if(color_map == NULL) return 0; // failure
  int i;
  unsigned char buf[3];
  for (i = 0; i < iminfo.num_colors; i++) {
    // ifs.read(buf, 3);  MSL
    ifs.read((char*) buf, 3);
    color_map[i].red = buf[0];
    color_map[i].green = buf[1];
    color_map[i].blue = buf[2];
  }
  return 1;
}

// for transparent images, force 0 to be the index of the transparent
// color, and color_map[0] to be white (all 1's)
// so transparent images work under X
void Am_GIF_Image::adjust_colormap()
{
  if (color_map != NULL) {
    if (iminfo.raw_transparent_color_index != 0)
      color_map[iminfo.raw_transparent_color_index] = color_map[0];

    color_map[0] = Am_RGB_Value(0xFF,0xFF,0xFF);
  }
}

// Load a gif file into an image structure.
Am_Generic_Image *Am_GIF_Image::Create(const char* filename)
{
  Am_GIF_Image *im = new Am_GIF_Image; 
  if (im == NULL) return NULL; // couldn't allocate the temp_image.
  GIF_Load_Info gli;
  
  // Buffer to hold a block of image data from the GIF file.
  unsigned char buf[256];

  // Open file for reading
  #if __MWERKS__
  ifstream ifs(filename, ios::in | ios::binary);
  #else
  ifstream ifs(filename, ios::in);
  #endif

  if(!ifs) {
    delete im;
    return NULL;
  }
  
  // all Am_GIF_Image images have depth 8
  im->iminfo.depth = 8;

  // Read GIF header
  // ifs.read((unsigned char*)&gli.hdr, GIF_SIG_LEN); MSL
  ifs.read((char*) &gli.hdr, GIF_SIG_LEN);
  
  // Check if image file format is acceptable
  if(strncmp(gli.hdr.signature, "GIF", 3) != 0 &&
     gli.hdr.version[0] != '8') {
    delete im;
    return NULL;
  }
  
  // Read the logical screen descriptor.  We only use the flags field.
  // ifs.read(buf, GIF_SD_SIZE); MSL
  ifs.read((char*) buf, GIF_SD_SIZE);
  int flag = buf[4];

  // If a global color table is present, read it.
  if(flag & HAS_CT) {
    if (!(im->load_gif_colormap(ifs, flag))) {
      delete im;
      return NULL;
    }
  }

  // Next comes a series of graphics blocks or special-purpose
  // blocks
  int byte = 0, read_an_image = 0;
  while(!read_an_image && (byte = ifs.get()) != GIF_TRAILER) {
      // Locate an image descriptor and read the image data
      int c;
      switch(byte) {
	case GIF_EXTSTART: {
	  c = ifs.get();
	  if(c == GIF_COMMENT) {
	  } else if (c == GIF_GCE) {
	    c = ifs.get(); // BlockSize always 4
	    // ifs.read(buf, GIF_GCE_SIZE); MSL
	    ifs.read((char*) buf, GIF_GCE_SIZE);
	    flag = buf[GIF_GCE_PACKED];
	    if (flag & TRANSPARENT) {
	      im->iminfo.transparent = true;
	      im->iminfo.raw_transparent_color_index = buf[GIF_GCE_COLORINDEX];
	    }
	  } else {
	    int blksize = ifs.get();
	    // ifs.read(buf, blksize); MSL
	    ifs.read((char*) buf, blksize);
	  }
	  while((c = ifs.get()) != 0)
	    //ifs.read(buf, c); MSL
	    ifs.read((char*) buf, c);
	  break;
	}  
	case GIF_IMGSTART: {
	  // Read GIF image information
	  // ifs.read(buf, GIF_ID_SIZE); MSL
	  ifs.read((char*) buf, GIF_ID_SIZE);
	  im->iminfo.width = (buf[5] << 8) + buf[4];
	  im->iminfo.height = (buf[7] << 8) + buf[6];
	  flag = buf[8];
	  
	  gli.interlaced = (flag & INTERLACED) ? 1 : 0;
	  
	  // Allocate memory for the GIF image and read in GIF image 
	  // into this array
	  im->imdata = new unsigned char[(long)(im->iminfo.width)
					*(long)(im->iminfo.height + 10)];
	  if(im->imdata == NULL) { // failed to allocate image data
	    delete im;
	    return NULL;
	  }
	  
	  // Read local color table, if any
	  if(flag & HAS_CT) {
	    if (!(im->load_gif_colormap(ifs, flag))) {
	      delete im;
	      return NULL;
	    }
	  }

	  // force index of transparent color to be 0
	  // to support X stipple raster ops
	  if (im->iminfo.transparent) {
	    im->adjust_colormap();
	  }

	  // Process the encoded data stream from the file.
	  if (!(im->process_gif(ifs, gli))) {
	    cerr << "** error reading gif file.\n";
	    delete im;
	    return NULL;
	  };
	  read_an_image = 1;
	  break;
	}
	default: {
	  cerr << "** error reading gif file.\n";
	  delete im;
	  return NULL;
	}
      }
    }
  // Success!
  return im;
}

// process_gif
// Processes a LZW data stream from a GIF file. Decoded data is
// placed in "image" array.
// return 0 for failure, 1 for success.

int Am_GIF_Image::process_gif(ifstream &ifs, GIF_Load_Info& gli)
{
  // Buffer to hold a block of image data from the GIF file.
  unsigned char buf[256];
  
  unsigned short count, input_code, bitpos = 0;
  unsigned long val = 0, tmp;
  unsigned int i;

  unsigned short data_mask = iminfo.num_colors - 1;

  // Originally the following three 4KB buffers were stack-allocated.
  // but gcc -fPIC (version 2.7.0, HP/UX 9.x) produced code for it that
  // barfed the HP assembler.  So we'll heap-allocate the buffers instead.
  unsigned short *prefix = new unsigned short[GIF_TBLSIZE];
  unsigned short *suffix = new unsigned short[GIF_TBLSIZE];
  unsigned short *stack = new unsigned short[GIF_TBLSIZE];

  unsigned short istk = 0;
  
  gli.x = 0; gli.y = 0; // initial values
  
  // Next byte is the initial number of bits used for LZW codes
  unsigned short init_code_size = ifs.get();

  if(init_code_size < 2 || init_code_size > 8) {
    delete [] prefix;  delete [] suffix;  delete [] stack;
    return 0;
  }

  unsigned short clear_code = 1 << init_code_size;
  unsigned short eoi_code = clear_code + 1;
  unsigned short free_code = clear_code + 2;
  
  // The GIF specification says that, for decompression, the code
  // size should be one bit longer than the initial code size.
  unsigned short code_size = init_code_size + 1;
  
  // Bit mask needed to extract data bits corresponding to the
  // code size.
  unsigned short max_code = 1 << code_size;
  unsigned short read_mask = max_code - 1;

  ifs.clear(0);
  
  for(count = ifs.get(); count > 0; count = ifs.get()) {
    // Read a block of data into the buffer
    // ifs.read(buf, count); MSL
    ifs.read((char*) buf, count);
    if (ifs.fail()) {
       delete [] prefix;  delete [] suffix;  delete [] stack;
       return 0;
    }
    
    // Process the contents of the buffer
    for(i = 0; i < count; i++) {
      tmp = buf[i];
      val |= (tmp << bitpos);
      bitpos += 8;
      
      while(bitpos >= code_size) {
	unsigned short curcode = (unsigned short)(val & read_mask);
	unsigned short oldcode;
	unsigned short pixel;
	val >>= code_size;
	bitpos -= code_size;
	if (curcode == eoi_code) {
	  delete [] prefix;  delete [] suffix;  delete [] stack;
	  return 1;
	}
	if (curcode == clear_code) {
	  free_code = (1 << init_code_size) + 2;
	  code_size = init_code_size + 1;
	  max_code = 1 << code_size;
	  read_mask = max_code - 1;
	  istk = 0;
	  oldcode = 0xffff;
	} else {
	  if (oldcode == 0xffff) {
	    // First code, after initialization, is a raw pixel value
	    pixel = curcode & data_mask;
	    add_to_image(gli, pixel);
	    oldcode = curcode;
	  } else {
	    input_code = curcode;
	    if (curcode >= free_code) {
	      // Code is not in table yet. Save last character in stack
	      stack[istk++] = (unsigned char)pixel;
	      curcode = oldcode;
	    }
	    while(curcode > data_mask) {
	      // [eab] I don't know enough about the LZW algorithm 
	      // to introduce more precise range tests than the following
	      // on curcode and istk
	      if (curcode >= GIF_TBLSIZE || istk >= GIF_TBLSIZE - 2) {
	        delete [] prefix;  delete [] suffix;  delete [] stack;
	        return 0; // corrupt GIF
	      }

              // Save bytes corresponding to compression code in stack for 
              // later use.
              stack[istk++] = suffix[curcode];
              curcode = prefix[curcode];
	    }
	    
	    pixel = curcode & data_mask;
	    stack[istk++] = (unsigned char)pixel;
	    
	    // Add decoded string of bytes to image
	    short j;
	    for(j = istk - 1; j >= 0; j--)
	      add_to_image(gli, stack[j]);
	    
	    istk = 0;
	    
	    // Add current information to decompression tables
	    prefix[free_code] = oldcode;
	    suffix[free_code] = (unsigned char)pixel;
	    oldcode = input_code;
	    
	    // Adjust code size, if necessary.
	    free_code++;
	    if (free_code >= max_code) {
	      if (code_size < GIF_MAXCODE) {
		code_size++;
		max_code *= 2;
		read_mask = max_code - 1;
	      }
	    }
	  }
	}
      }
    }
  }
  delete [] prefix;  delete [] suffix;  delete [] stack;
  return 1;
}

// Add a pixel to the image

void Am_GIF_Image::add_to_image(GIF_Load_Info &gli,
				unsigned short pixval)
{
  // force index of transparent color to be 0
  unsigned char pixchar = (unsigned char) pixval;
  if (iminfo.raw_transparent_color_index != 0) {
    if (pixval == 0) pixchar = (unsigned char) iminfo.raw_transparent_color_index;
    else if (pixval == iminfo.raw_transparent_color_index) pixchar = 0;
  }

  imdata[gli.x + iminfo.width * gli.y] = pixchar;
  
  // Adjust the coordinates
  gli.x++;
  if(gli.x == iminfo.width) {
    gli.x = 0;
    if(!gli.interlaced) gli.y++;
    else {
      switch(gli.pass)
	{
	case 1:  // Every 8th row, start at row 0
	  gli.y += 8;
	  if(gli.y >= iminfo.height) {
	    gli.y = 4;
	    gli.pass++;
	  }
	  break;
	  
	case 2:  // Every 8th row, start at row 4
	  gli.y += 8;
	  if(gli.y >= iminfo.height) {
	    gli.y = 2;
	    gli.pass++;
	  }
	  break;
	  
	case 3:  // Every 4th row, start at row 2
	  gli.y += 4;
	  if(gli.y >= iminfo.height) {
	    gli.y = 1;
	    gli.pass++;
	  }
	  break;
	  
	case 4:  // Every 2nd row, start at row 1
	  gli.y += 2;
	  break;
	}
    }
  }
}
