/* Copyright (c) 2010 MJ Rutter 
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 * 
 * 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., 51 Franklin Street,
 * Fifth Floor, Boston, MA  02110-1301, USA.
 */ 

/* 6/06: added support for GIFs whose only colour-map is local, not global */

/* 8/12 Fixed bug so that GIF89s are read! */

#include <stdio.h>
#include <stdlib.h>

#include "bmp2eps.h"

unsigned int de_lzw(unsigned char *comp, unsigned char *out, unsigned int len,
		    int cs);
static void de_gifinterlace(struct bitmap *image);

void gifread(FILE *in, struct bitmap *image){
  unsigned char buff[8],flags,len,*rawz;
  int gcm,lxoff,lyoff,lw,lh,lzwcs,ncols,debug,interlaced;
  unsigned datalen,width,height;
  static unsigned char trans;

  debug=image->flags&DEBUG_MASK;

  if (debug) fprintf(stderr,"GIF file detected\n");

  fread(buff,3,1,in);
  buff[3]=0;

  if ((buff[0]=='8')&&((buff[1]=='7')||(buff[1]=='9'))&&(buff[2]=='a')){
    if (debug>1) fprintf (stderr,"GIF8%ca\n",buff[1]);
  }
  else {fprintf(stderr,"Not a GIF file\n"); exit(1);}

  fread(buff,4,1,in);

  width=((unsigned)buff[1]<<8)+buff[0];
  height=((unsigned)buff[3]<<8)+buff[2];

  if (debug) fprintf(stderr,"Size %dx%d\n",width,height);

  fread(&flags,1,1,in);

  if (debug>2) fprintf(stderr,"Global colour map=%d\n",(flags&0x80)>>7);
  if (debug>2) fprintf(stderr,"Colour resolution=%d\n",(flags&0x70)>>4);
  if (debug>2) fprintf(stderr,"Colour depth     =%d\n",(flags&0x7));

  gcm=((flags&0x80)>>7);
  ncols=2<<(flags&0x7);

  if (debug>1) fprintf(stderr,"Global number of colours=%d\n",ncols);

  fread(buff,2,1,in);

  if (gcm){
    image->palette=malloc(ncols*3);
    if (!image->palette){fprintf(stderr,"Malloc error\n");exit(1);}
    fread(image->palette,ncols*3,1,in);
    image->pal_len=ncols;
  }


  fread(&flags,1,1,in);

  while (flags==0x21){
    fread(&flags,1,1,in);
    if (debug>2) fprintf(stderr,"Extension block %d\n",(int)flags);
    if ((int)flags==249){
      fread(&flags,1,1,in);
      if (flags!=4){
        fprintf(stderr,"Unexpected extension length in GIF\n");
        exit(1);
      }
      fread(&flags,1,1,in);
      if (flags&1){
        image->trans=&trans;
        fseek(in,2,SEEK_CUR);
        fread(&flags,1,1,in);
        *image->trans=flags;
        if (debug>1) fprintf(stderr,"Transparent colour index at %d\n",
                              (int)flags);
      } else fseek(in,3,SEEK_CUR);
      fread(&flags,1,1,in);
      if (flags){
        fprintf(stderr,"Unexpected extension data in GIF\n");
        exit(1);
      }
    }
    else do{
      if (!fread(&flags,1,1,in)){
	fprintf(stderr,"Unexpected end of file\n");
        exit(1);
      }
      fseek(in,(long)flags,SEEK_CUR);
    }while(flags);
    fread(&flags,1,1,in);
  }

  if (flags==0x2c) if (debug>2) fprintf (stderr,"Local descriptor\n");

  fread(buff,4,1,in);
  lxoff=((unsigned)buff[1]<<8)+buff[0];
  lyoff=((unsigned)buff[3]<<8)+buff[2];

  if (debug>2) fprintf(stderr,"Local offset %dx%d\n",lxoff,lyoff);

  fread(buff,4,1,in);
  lw=((unsigned)buff[1]<<8)+buff[0];
  lh=((unsigned)buff[3]<<8)+buff[2];

  if (debug>2) fprintf(stderr,"Local size %dx%d\n",lw,lh);

  fread(&flags,1,1,in);

  if (flags&0x80) {
    if (debug>2) fprintf(stderr,"Local colour map\n");
    ncols=2<<(flags&0x7);
    if (debug>1) fprintf(stderr,"Local number of colours=%d\n",ncols);
    if (gcm) free (image->palette);
    image->palette=malloc(ncols*3);
    if (!image->palette){fprintf(stderr,"Malloc error\n");exit(1);}
    fread(image->palette,ncols*3,1,in);
    image->pal_len=ncols;
  }

  interlaced=flags&0x40;

  if (interlaced&&(debug>1)) fprintf(stderr,"Interlaced\n");

  if (lxoff || lyoff || (lw!=width) || (lh!=height)){
    fprintf(stderr,"Exiting: local and global sizes differ.\n");
    exit(1);
  }

  fread(&flags,1,1,in);
  lzwcs=flags+1;
  if (debug>1) fprintf(stderr,"Initial code size=%d\n",lzwcs);

  if (!(rawz=malloc((width*height*3)/2))){
    fprintf(stderr,"Malloc error on rawz\n");
    exit(1);
  }

  datalen=0;
  fread(&len,1,1,in);
  while(len){
    fread(rawz+datalen,len,1,in);
    datalen+=len;
    fread(&len,1,1,in);
  }
  if (debug>1) fprintf(stderr,"\nTotal GIF data length=%d\n",datalen);


  if (!(image->data=malloc(width*height))){
    fprintf(stderr,"Malloc error on raw in gifread\n");
    exit(1);
  }

  datalen=de_lzw(rawz,image->data,width*height,lzwcs);

  if (datalen!=width*height){
    fprintf(stderr,"LZW decode error in readgif. Expecting %d bytes, "
	    "received %d.\n",width*height,datalen);
    exit(1);
  }

  free(rawz);

  image->data_len=width*height;
  image->height=height;
  image->width=width;
  image->type=PALETTE;
  image->colour=PALETTE;
  image->depth=8;

  if (interlaced) de_gifinterlace(image);

}

static void de_gifinterlace(struct bitmap *image){
  int i,j;
  unsigned char *out,*in,*outp;

  out=malloc(image->height*image->width);
  if (!out){
    fprintf(stderr,"Malloc error in de_gifinterlace\n");
    exit(1);
  }

  in=image->data;

  /* First pass is eights */

  outp=out;
  for(i=0;i<image->height;i+=8){
    for(j=0;j<image->width;j++) *outp++=*in++;
    outp+=7*image->width;
  }

  /* Next is fours */

  outp=out+4*image->width;
  for(i=4;i<image->height;i+=8){
    for(j=0;j<image->width;j++) *outp++=*in++;
    outp+=7*image->width;
  }

  /* Next is twos */

  outp=out+2*image->width;
  for(i=2;i<image->height;i+=4){
    for(j=0;j<image->width;j++) *outp++=*in++;
    outp+=3*image->width;
  }

  /* Next is ones */

  outp=out+image->width;
  for(i=1;i<image->height;i+=2){
    for(j=0;j<image->width;j++) *outp++=*in++;
    outp+=image->width;
  }

  free(image->data);
  image->data=out;
}
