/*
 * This software is copyrighted as noted below.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is
 * preserved on all copies.
 *
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the author, who may or may not act on them as he desires.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 */
/*
 * gif.c - interface with Compuserve's GIF format.
 *
 * Authors:     Raul Rivero
 *              Mathematics Dept.
 *              University of Oviedo
 * Date:        Wed Jan 8 1992
 * Copyright (c) 1992, Raul Rivero
 *
 */

#include <lug.h>
#include <lugfnts.h>

#define GIFHEADER               "GIF87a"
#define NEWGIFHEADER            "GIF89a"
#define GIFIMGSEPAR             ','
#define ENDGIF                  ';'
#define GIFEXISTCOLOR           (1 << 7)
#define GIFINTERLAZE            (1 << 6)

byte *read_gif_screen_hdr();
byte *read_gif_image_hdr();
byte *unblock();

/*int oldread;*/
static byte *ptr_image;
static int image_size;
static int lug_read_code_position;
static int write_gif89_flag = FALSE;
static int reset_read_pixel;

extern int LUGverbose;

read_gif_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "rb" );
  else error( 20 );

  /* Read the bitmap */
  read_gif( handle, bitmap );
  rm_compress();

  /* Close the file */
  Fclose( handle );
}

read_gif( handle, image )
FILE *handle;
bitmap_hdr *image;
{
  int xsize, ysize;
  byte *globalcmap, *localcmap;
  int mask;
  int codesize;
  int totalsize;
  int globalcolors, localcolors;
  int localmask, globalmask;

  /*
   * Used inside read_code(...)
   */
  lug_read_code_position = 0;

  /*
   * Read GIF headers ( we skip many parameters ).
   */
  read_gif_hdr( handle );
  globalcmap = read_gif_screen_hdr( handle, &globalcolors, &globalmask );
  localcmap = read_gif_image_hdr( handle, &localcolors, &localmask,
                                  &xsize, &ysize );

  /*
   * We only use one color map.
   */
  if ( localcolors ) {
    mask = localmask;
    image->cmap = localcmap;
    if ( globalcolors )
      free( globalcmap );
  }else
    if ( globalcolors ) {
      mask = globalmask;
      image->cmap = globalcmap;
    }else error( 13 );

  /*
   *  Original ( root ) codesize.
   */
  codesize= fgetc( handle );

  /*
   * Fill our header.
   */
  totalsize = xsize * ysize;
  image->magic = LUGUSED;
  image->xsize = xsize;
  image->ysize = ysize;
  image->colors = ( localcolors ? localcolors : globalcolors );
  image->depth = no_bits( image->colors ) + 1;
  image->r = (byte *) Malloc( totalsize );
  uncode_gif( handle, codesize, mask, image );
}

#define READCODE()      { code = read_code( ptrblocks, datamask,    \
                                            &offset, codesize );    \
                          ptrblocks += offset; }

uncode_gif( handle, codesize, mask, image )
FILE *handle;
int codesize, mask;
bitmap_hdr *image;
{
  int endblock;
  int clearcode;
  int offset;
  int freecode;
  int code;
  int current, old;
  int datamask;
  int codetop;
  int orig_codesize;
  short *sufix, *prefix;
  byte *blocks, *ptrblocks;
  int count = 0;
  byte *stack;
  int k;
  int totalsize;
  int position;

  /*
   * Allocate memory for internal buffers.
   */
  stack  = (byte *) Malloc( 4096 );
  sufix  = (short *) Malloc( 4096 * sizeof(short) );
  prefix = (short *) Malloc( 4096 * sizeof(short) );

  /*
   * Compute predefined values for uncompress.
   */
  clearcode = (1 << codesize);
  endblock = clearcode + 1;
  freecode = clearcode + 2;
  orig_codesize = ++codesize;
  codetop = 1 << codesize;
  datamask = codetop - 1;

  /*
   * Unblock the raster information.
   */
  ptrblocks = blocks = (byte *) unblock( handle );

  totalsize = image->xsize * image->ysize;

  /*
   * and now, ... UNPACK !!!.
   */
  VPRINTF( stderr, "Uncompressing GIF raster information\n" );
  READCODE();  /* read first code */
  position = push_gif( image->r, 0, TRUE );  /* reset the internal buffer */
  while ( code != endblock ) {
    if ( code == clearcode ) {
      /*
       * Current code is clear so we reinitialize our tables.
       */
      codesize = orig_codesize;
      freecode = clearcode + 2;
      codetop = 1 << codesize;
      datamask = codetop - 1;
      READCODE();
      old = current = code;
      /* Next code to 'clear' is a root code */
      k = code & mask;
      position = push_gif( image->r, k, FALSE );
    }else {
      /*
       * We have a normal code.
       */
      current = code;
      /*
       * If it's a new code then we need add the last translation.
       */
      if( !(code < freecode) ) {
        current = old;
        stack[count++] = (byte) k;
      }

      /*
       * mask equals to 'last_root_code' so while current
       * code greater [than mask] we haven't a root code
       * and continue adding values into ioutput.
       */
      while ( current > mask ) {
        stack[count++] = sufix[current];
        current = prefix[current];
      }
      k = current & mask;
      stack[count] = k;
      for ( ; count >= 0; count-- )
        position = push_gif( image->r, (int) stack[count], FALSE );
      count = 0;
      prefix[freecode  ] = old;
      sufix [freecode++] = k;
      old = code;
      /*
       * Check if we use all posibles values ( for this
       * codesize ).
       */
      if ( freecode >= codetop && codesize != 12 ) {
        codesize++;
        codetop = 1 << codesize;
        datamask = codetop - 1;
      }
    }
    READCODE();
    if ( position > totalsize )
      /*error(6)*/ break;
  }

  /*
   * Free original GIF raster information and
   * internal buffers.
   */
  free( blocks );
  free( prefix );
  free( sufix );
  free( stack );
}

read_code( buffer, mask, offset, codesize )
byte *buffer;
int mask;
int *offset;
int codesize;
{
  int aux = 0;
  int moveptr;

  aux  = *buffer++;
  aux |= ( *buffer++ << 8 );
  if ( codesize > 7 )
    aux |= ( *buffer++ << 16 );
  aux >>= lug_read_code_position;
  lug_read_code_position += codesize;
  moveptr = lug_read_code_position / 8;
  if ( moveptr ) {
    *offset = moveptr;
    lug_read_code_position %= 8;
  }else *offset = 0;

  return( aux & mask );
}

push_gif(buffer, indexx, reset )
byte *buffer;
int indexx;
int reset;
{
  static int offset;
  static byte *ptr;
  static byte *baseptr;

  if ( reset ) {
    ptr = baseptr = buffer;
    offset = 0;
  }else {
    *ptr++ = (byte) indexx;
    return ++offset;
  }
}

read_gif_hdr( handle )
FILE *handle;
{
  char buffer[6];

  Fread( buffer, 6, 1, handle);
  if ( strncmp( buffer, GIFHEADER, 6 ) && 
       strncmp( buffer, NEWGIFHEADER, 6 ) )
    error( 5 );
}

byte *read_gif_screen_hdr( handle, colors, mask )
FILE *handle;
int *colors;
int *mask;
{
  int swidth, sheight;
  byte buffer[7];
  byte *cmap;
  int cmapflag;
  int bitsperpixel;
  int background;

  VPRINTF( stderr, "Reading GIF header\nScreen...\n" );

  Fread( buffer, 7, 1, handle);

  /* Screen size */
  swidth   = (buffer[1] << 8) | buffer[0];
  sheight  = (buffer[3] << 8) | buffer[2];

  cmapflag = buffer[4] & GIFEXISTCOLOR;
  bitsperpixel = (buffer[4] & 7) + 1;
  *colors = 1 << bitsperpixel;
  *mask = *colors - 1;
  background= buffer[5];

  VPRINTF( stderr, "\tdimensions: %dx%d\n", swidth, sheight );
  VPRINTF( stderr, "\tbits per pixel: %d ( %d colors )\n", bitsperpixel, *colors );

  if ( buffer[6] )
    errornull( 6 );

  if ( cmapflag ) {
    VPRINTF( stderr, "\twith global color map\n" );
    cmap = (byte *) Malloc( 3 * *colors );
    Fread( cmap, *colors, 3, handle );
    VPRINTF( stderr, "\tbackground index: %d ( %d-%d-%d )\n", background,
             *(cmap+background*3), *(cmap+1+background*3), *(cmap+2+background*3) );
  }else {
    VPRINTF( stderr, "\tno background color" );
  }

  return cmap;
}

byte *read_gif_image_hdr( handle, colors, mask, width, height )
FILE *handle;
int *colors;
int *mask;
int *width, *height;
{
  int c;
  int left, right;
  byte buffer[9];
  byte *cmap;
  int cmapflag;
  int interlace;
  int bitsperpixel;

  VPRINTF( stderr, "Reading GIF image header\nImage...\n" );

  /* Get the id. */
  c = fgetc( handle );

  /* Is the image descriptor ? */
  if ( c != GIFIMGSEPAR) {
    if (c == '!') {
      int c;

      /* Ok, we have an extension block => skip it */
      read_gif_extension( handle );

      /* Now, we need the real image description ... */
      c = fgetc(handle);
      if ( c != GIFIMGSEPAR)
        errornull( 5 );
    }else {
      VPRINTF( stderr, "Unknown block description\n" );
      error( 99 );
    }
  }

  /* Read the Image Descriptor Block */
  Fread( buffer, 9, 1, handle );

  left    = (buffer[1] << 8) | buffer[0];
  right   = (buffer[3] << 8) | buffer[2];
  *width  = (buffer[5] << 8) | buffer[4];
  *height = (buffer[7] << 8) | buffer[6];
  cmapflag  = buffer[8] & GIFEXISTCOLOR;
  interlace = buffer[8] & GIFINTERLAZE;

  VPRINTF( stderr, "\tleft-right: %d %d\n", left, right );
  VPRINTF( stderr, "\tdimensions: %dx%d\n", *width, *height );

  if ( interlace )
    errornull( 17 );

  if ( cmapflag ) {
    bitsperpixel = ( buffer[8] & 7 ) + 1;
    *colors = 1 << bitsperpixel;
    *mask = *colors - 1;
    cmap = (byte *) Malloc( 3 * *colors );
    Fread( cmap, *colors, 3, handle );
    VPRINTF( stderr, "\twith local color map\n" );
  }else {
    *colors = 0;
    VPRINTF( stderr, "\twithout local color map\n" );
  }

  return cmap;
}

void
read_gif_extension( handle )
FILE *handle;
{
  int id, size;
  
  switch ( id = fgetc(handle) ) {
    case 0xf9:
	/* 
         * A Graphic Control Extension
         */
        /* Get the size of the block ( +1 with the block terminator ) */
        size = fgetc( handle );
        /* Skip the block */
        while ( size-- ) 
          (void)fgetc( handle );
        VPRINTF( stderr, "\tskipping a Graphic Control Extension" );
        break;
    case 0xfe:
        /* 
         * A Comment Extension 
         */
        VPRINTF( stderr, "\tskipping a Comment Extension" );
        break;
    case 0x01:
        /* 
         * A Plain Text Extension
         */
        /* Get the size of the header */
        size = fgetc( handle );
        /* Skip the header block */
        while ( size-- ) 
          (void)fgetc( handle );
        VPRINTF( stderr, "\tskipping a Plain Text Extension" );
        break;
    case 0xff:
        /* 
         * An Application Extension 
         */
        /* Get the size of the header */
        size = fgetc( handle );
        /* Skip the header block */
        while ( size-- ) 
          (void)fgetc( handle );
        VPRINTF( stderr, "\tskipping a Application Extension" );
        break;
    default:
	fprintf( stderr, "Unkown Extension Block (%d)\n", id );
        error(99);
  }

  /*
   * Skip all data blocks.
   */
  size = fgetc(handle);
  while ( size ) {
    while ( size-- ) 
      (void)fgetc( handle );
      /* Size of the next block */
    size = fgetc(handle);
  }
  VPRINTF( stderr, ", completed\n" );
}

byte *unblock( handle )
FILE *handle;
{
  long position;
  int totalsize;
  byte *out, *ptr;
  int size;
  int count = 0;

  VPRINTF( stderr, "Unblocking GIF file\n" );
  /*
   * We need read the raster information and strip the block
   * size marks. GIF files can store more than one image but
   * this is not usually ( so we move handle to the end and
   * supose that size ).
   */
  position = ftell( handle );
  fseek( handle, 0L, 2 );
  totalsize = ftell( handle ) - position;
  fseek( handle, position, 0 );

  ptr = out = (byte *) Malloc( totalsize );

  /*
   * Format is:  <size><...block...><size><...block...>[...]<0>
   */
  size = fgetc( handle );
  while ( size ) {
    /* Check if no problems with space */
    count += size;
    if ( count > totalsize )
      errornull( 7 );
    /* No problems => read the block */
    Fread( ptr, size, 1, handle );
    ptr += size;
    size = fgetc( handle );
  }

  return out;
}

write_gif_file( name, image )
char *name;
bitmap_hdr *image;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "wb" );
  else handle = stdout;

  /* Write the bitmap */
  write_gif( handle, image );

  /* Close the file */
  Fclose( handle );

}

write_gif(handle, image)
FILE *handle;
bitmap_hdr *image;
{
  int codesize;
  int read_pixel();

  if ( image->magic != LUGUSED )
    error( 19 );

  /* GIF only support mapped images */
  if ( image->depth > 8 )
    error( 15 );

  if ( write_gif89_flag ) {
    VPRINTF(stderr, "Writing GIF(89) headers\n");
  }else {
    VPRINTF(stderr, "Writing GIF(87) headers\n"); 
  }
  
  /*
   * Write GIF headers.
   */
  write_gif_hdr( handle );
  write_gif_screen_hdr( handle, image );
  write_gif_cmap( handle, image );
  if ( write_gif89_flag ) {
    /* Write the transparency reference */
    write_gif_graphics_ctrl_ext( handle, image, *(image->r) );
  } 
  write_gif_image_hdr( handle, image );

  /*
   * Now store the original code size.
   */
  codesize = image->depth;
  fputc( codesize, handle );

  /*
   * Image size.
   */
  image_size = image->xsize * image->ysize;
  /* el primer codigo libre sera de +1 bits */
  codesize++;
  /*
   * Compress the image.
   */
  VPRINTF(stderr, "Compressing raster information\n");
  ptr_image = image->r;
  reset_read_pixel = TRUE;
  compress( codesize, handle, read_pixel );

  /*
   * Block with a size of 0 bytes.
   */
  fputc( 0, handle );

  /*
   * End of gif file.
   */
  fputc( ENDGIF, handle );      /* fin del fichero GIF */
}

write_gif_hdr(handle)
FILE *handle;
{
  if ( write_gif89_flag ) {
    Fwrite( NEWGIFHEADER, 6, 1, handle );
  }else {
    Fwrite( GIFHEADER, 6, 1, handle );
  }
}

write_gif_screen_hdr(handle, image)
FILE *handle;
bitmap_hdr *image;
{
  byte buffer[7];
  byte *ptr;

  ptr = buffer;
  /* Width ( of screen ) */
  *ptr++ = LSB(image->xsize);
  *ptr++ = MSB(image->xsize);
  /* Height ( of screen ) */
  *ptr++ = LSB(image->ysize);
  *ptr++ = MSB(image->ysize);
  /* Colors */
  *ptr++ = (1 << 7) | (no_bits(image->colors) << 4) | (no_bits(image->colors));
  /* Background color */
  *ptr++ = 0;
  /* End of block */
  *ptr++ = 0;

  Fwrite( buffer, 7, 1, handle);
}

write_gif_cmap(handle, image)
FILE *handle;
bitmap_hdr *image;
{
  Fwrite(image->cmap, 3, image->colors, handle);
}

write_gif_graphics_ctrl_ext( handle, image, transparency )
FILE *handle;
bitmap_hdr *image;
int transparency;
{
  byte buffer[8];
  byte *ptr;

  if ( transparency >= 0 ) {
    /* Only write this extension if we need it */
    ptr = buffer;
    /* Fill with 0's */
    bzero( ptr, 8 );
    /* The extension introducer */
    *ptr++ = 0x21;
    /* This is ... a Graphics Control Extension !!! ( yeah?, really ?! :) */
    *ptr++ = 0xf9;
    /* A block of 4 bytes */
    *ptr++ = 4;
    /* 
     * Packed field:
     *                 o We'll give the transparency index => bit 0 = 1
     *                 o I'll wait for a user input => bit 1 = 1
     */
    *ptr++ = 3; /* 00000011 */
    /* No delay */
    ptr += 2;
    /* The transparency color */
    *ptr++ = transparency;
    /* ... and with the end of the block. */
    Fwrite( buffer, 8, 1, handle); 
  }
}

write_gif_image_hdr(handle, image)
FILE *handle;
bitmap_hdr *image;
{
  byte buffer[10];
  byte *ptr;

  ptr = buffer;
  /* Separator */
  *ptr++ = GIFIMGSEPAR;
  /* Left adjust */
  *ptr++ = 0;
  *ptr++ = 0;
  /* Right adjust */
  *ptr++ = 0;
  *ptr++ = 0;
  /* Width ( of image ) */
  *ptr++ = LSB(image->xsize);
  *ptr++ = MSB(image->xsize);
  /* Height ( of image ) */
  *ptr++ = LSB(image->ysize);
  *ptr++ = MSB(image->ysize);
  /* Word of definition ( without colormap, ... ) */
  *ptr++ = 7;

  Fwrite( buffer, 10, 1, handle);
}

read_pixel()
{
  static int size;
  static byte *ptr;

  if ( reset_read_pixel ) {
    /*
     * It's a new pointer, so we need stored.
     */
    ptr = ptr_image;
    size = image_size;
    reset_read_pixel = FALSE;
  }

  if ( size ) {
    /*
     * We have pixels ...
     */
    size--;
    return( (int)(*ptr++) );
  }else {
    return EOF; /* no more pixels, this is the end */
  }
}

void
set_write_gif89_flag()
{
  write_gif89_flag = TRUE;
}

void
unset_write_gif89_flag()
{
  write_gif89_flag = FALSE;
}

