/*
 *  gifs.c - GIF-saving routines
 *
 *  Copyright (c) 1994 Andreas Beck (becka@hp.rz.uni-duesseldorf.de)
 *  Feel free to use this code in your own applications - but please
 *  do not remove the (c)-notice or this header.
 *  Please report bugs or useful modifications to the above address.
 *
 */
         
/*
 *  This routines are 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  gifs.c,v 0.0.1 1994/05/17 10:58:42
 */

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

static int bpp2,loaded,realbytes,EC,IC,lastcol;
static unsigned char anz;
static int Code[4096],Anflink[4096];
static unsigned char Col[4096],Anf[4096];

struct gifhead {char ID[6];short int wide,high;char gidb,backcol,unbek;};
static struct gifhead gih;
struct imghead {short int dx,dy,br,hi;char lidb;};
static struct imghead imh;

typedef struct paltable { unsigned char r,g,b; } dregs[256];

static int bitsinbuf,numbits,numb2;
static unsigned long bitbuf;

static unsigned char outbuf[255];

static void putbits(int handle,int wert)
{ bitbuf+=(wert<<bitsinbuf);bitsinbuf+=numbits;
  while(bitsinbuf>=8)
  { if (realbytes==anz) {write(handle,&anz,1);
                         write(handle,outbuf,anz);realbytes=0;}
    outbuf[realbytes++]=(unsigned char)bitbuf;
    bitbuf>>=8;bitsinbuf-=8;}}

static void flush(int handle)
{if (bitsinbuf)
 {if (realbytes==anz) {write(handle,&anz,1);write(handle,outbuf,anz);anz=1;
		       write(handle,&anz,1);write(handle,&bitbuf,1);goto xx1;}
  else {outbuf[realbytes]=(unsigned char)bitbuf;realbytes++;}}
  write(handle,&realbytes,1);write(handle,outbuf,realbytes);
  xx1:realbytes=0;write(handle,&realbytes,1);}

static int Last(int first)
{while(1){if (Anflink[first]==4096) return(first);else first=Anflink[first];}}

static void encode(int handle,int getpixel(void))
{ register int x,neu;int alt,z;

  alt=neu=x=getpixel();
  redo: 
  for(x=0;x<256;x++) Anflink[x]=4096;

  if (neu==-1) {putbits(handle,EC);return;}
  else          putbits(handle,neu);

  x=getpixel();
  while(1)
  {if (lastcol>=numb2)
    {if (numbits<12) {numbits++;numb2<<=1;}
	        else {putbits(handle,IC);numbits=bpp2+1;numb2=1<<numbits;
		      lastcol=(1<<bpp2)+2;alt=neu=x;goto redo;}}
   if (x==-1) {putbits(handle,EC);break;}
   neu=z=x;x=getpixel();if (x!=-1)
   { while(1)
     { if (Anflink[z]>=lastcol) break;else z=Anflink[z];
       if (Code[z]==neu&&x==Col[z]) {neu=z;x=getpixel();if (x==-1) break;}}}
   if (neu==alt&&x==Anf[alt])
        {putbits(handle,lastcol);x=getpixel();Col[lastcol]=Anf[alt];neu=lastcol;}
   else {putbits(handle,neu    ); 	        Col[lastcol]=Anf[neu];}
   Code[lastcol]=alt;Anf[lastcol]=Anf[alt];
   Anflink[Last(Anf[alt])]=lastcol;Anflink[lastcol]=4096;
   alt=neu;lastcol++;
  }
}


/* This will save the actual Image.Getpixel must provide a stream of 
   pixel-values,terminated with a (-1).This may also be used as a generic
   compression engine ... */
int GIF_saveimg(int handle,int bpp,int getpixel(void))
{ int z;
  bitsinbuf=realbytes=loaded=0;bitbuf=0L;
  bpp2=bpp;if (bpp2<2) bpp2=2;	/* Minimum ... */
  numb2=1<<(numbits=bpp2+1);lastcol=1+(EC=1+(IC=1<<bpp2));

  for(z=0;z<(1<<bpp2);z++) {Col[z]=z;Code[z]=-1;Anf[z]=z;}

  write(handle,&bpp2,1);
  
  anz=255;putbits(handle,IC);
  
  encode(handle,getpixel);flush(handle);  

  return 0;
}


/* This will write a local image header and thereby start a Image-Chunk.
   This must be followed by the Image Data.The routine saveimg is kept
   seperately to allow update of the header (position or length may be
   unknown while writing is in progress ...) and to use the saveimg-
   function as a generic LZ-compression engine ... */
int GIF_savelhead(int handle,int br,int h,int bpp,dregs *pt)
{ 
  write(handle,",",1);

  imh.dx=0;imh.dy=0;imh.br=br;imh.hi=h;
  imh.lidb=(pt!=NULL)*0x80+(bpp2=bpp)-1;
  write(handle,&imh,9);

  if (imh.lidb&0x80)
  { bpp2=(imh.lidb&7)+1;write(handle,pt,sizeof(*pt[0])*(1<<bpp2)); } 

  return 0;
}


/* This will write a "EndOfGif"-Chunk which should be placed after the last
   image-chunk. */
int GIF_saveend(int handle)
{ if (write(handle,";",1)!=1) return -1;else return 0; }


/* This will write a GIF87a header which has to be at the very beginning
   of the file. */
int GIF_saveghead(int handle,int br,int h,int bpp,dregs *pt)
{ 
  gih.ID[0]='G';gih.ID[1]='I';gih.ID[2]='F';
  gih.ID[3]='8';gih.ID[4]='7';gih.ID[5]='a';
  gih.wide=br;gih.high=h;gih.backcol=0;

  gih.gidb=0x30+(bpp-1)+(pt!=NULL)*0x80;
  /* gih.unbek=bpp-1; This seems to cause trouble ... */
  gih.unbek=0;
  
  bpp2=bpp;
  if (write(handle,&gih,13)!=13) {close(handle);return -1;}

  if (gih.gidb&0x80)
  { bpp2=(gih.gidb&7)+1;write(handle,pt,sizeof(*pt[0])*(1<<bpp)); } 

  return 0;
}


/* This will save a whole GIF.The colour-map will be global and there
   will be only one Image in the file.If this does not suit your needs
   you will have to do it yourself using the above routines.These
   should be enough to write any GIF you want ...                      */

int GIF_save(char *name,int br,int h,dregs *pt,int bpp,int getpixel(void))
{ int handle;
  if ((handle=open(name,O_TRUNC|O_CREAT|O_WRONLY))==-1) return -1;
  if (GIF_saveghead(handle,br,h,bpp,pt)!=0) {close(handle);return -1;}
  if (GIF_savelhead(handle,br,h,bpp,NULL)!=0) {close(handle);return -1;}
  if (GIF_saveimg  (handle,bpp,getpixel)!=0) {close(handle);return -1;}
  if (GIF_saveend  (handle)!=0) {close(handle);return -1;}
  close(handle);return(0);
}
