/* $Id: gifsave.c,v 1.2 1998/07/05 16:29:56 sverrehu Exp $ */
/**************************************************************************
*
*  FILE            gifsave.c
*
*  DESCRIPTION     Routines to create a GIF-file. See README for
*                  a description.
*
*                  The functions were originally written using Borland's
*                  C-compiler on an IBM PC -compatible computer, but they
*                  are compiled and tested on Linux and SunOS as well.
*
*  WRITTEN BY      Sverre H. Huseby <[email protected]>
*
**************************************************************************/

#include <stdlib.h>
#include <stdio.h>
/* #include <unistd.h> */       /* (added by j.forkosh) to get STDOUT_FILENO*/
#include <string.h>             /* " */
/* --- windows-specific header info --- */
#ifndef WINDOWS                 /* -DWINDOWS not supplied by user */
 #if defined(_WINDOWS) || defined(_WIN32) || defined(WIN32) \
 ||  defined(DJGPP)            /* try to recognize windows compilers */ \
 ||  defined(_USRDLL)          /* must be WINDOWS if compiling for DLL */
   #define WINDOWS             /* signal windows */
 #endif
#endif
#ifdef WINDOWS                  /* " if filename=NULL passed to GIF_Create()*/
 #include <fcntl.h>            /* " OutFile=stdout used.  But Windows opens*/
 #include <io.h>               /* " stdout in char mode, and precedes every*/
                               /* " 0x0A with spurious 0x0D. */
 #if defined(_O_BINARY) && !defined(O_BINARY)  /* only have _O_BINARY */
   #define O_BINARY _O_BINARY  /* make O_BINARY available, etc... */
   #define setmode  _setmode
   #define fileno   _fileno
 #endif
 #if defined(_O_BINARY) || defined(O_BINARY)  /* setmode() now available */
   #define HAVE_SETMODE        /* so we'll use setmode() */
 #endif
#endif

/* #include "gifsave.h" */      /* (j.forkosh) explcitly include header */
enum GIF_Code {
   GIF_OK = 0,
   GIF_ERRCREATE,
   GIF_ERRWRITE,
   GIF_OUTMEM
};

int  GIF_Create(const char *filename, int width, int height,
               int numcolors, int colorres);
void GIF_SetColor(int colornum, int red, int green, int blue);
void GIF_SetTransparent(int colornum);  /* (added by j.forkosh) */
int  GIF_CompressImage(int left, int top, int width, int height,
                      int (*getpixel)(int x, int y));
int  GIF_Close(void);
/* --- end-of-header gifsave.h --- */


/**************************************************************************
*                                                                        *
*                       P R I V A T E    D A T A                         *
*                                                                        *
**************************************************************************/

typedef unsigned Word;          /* at least two bytes (16 bits) */
typedef unsigned char Byte;     /* exactly one byte (8 bits) */

/* used by IO-routines */
static FILE *OutFile = NULL;    /* file to write to */
static Byte *OutBuffer = NULL;  /* (added by j.forkosh) */
static int isCloseOutFile = 0;  /* " */
#if !defined(MAXGIFSZ)          /* " */
 #define MAXGIFSZ 131072       /* " max #bytes comprising gif image */
#endif                          /* " */
int gifSize = 0;                /* " #bytes comprising gif */
int maxgifSize = MAXGIFSZ;      /* " max #bytes written to OutBuffer */
extern int  iscachecontenttype; /* " true to cache mime content-type */
extern char contenttype[2048];  /* " content-type:, etc. buffer */

/* used when writing to a file bitwise */
static Byte Buffer[256];        /* there must be one more than `needed' */
static int  Index,              /* current byte in buffer */
           BitsLeft;           /* bits left to fill in current byte. These
                                * are right-justified */

/* used by routines maintaining an LZW string table */
#define RES_CODES 2

#define HASH_FREE 0xFFFF
#define NEXT_FIRST 0xFFFF

#define MAXBITS 12
#define MAXSTR (1 << MAXBITS)

#define HASHSIZE 9973
#define HASHSTEP 2039

#define HASH(index, lastbyte) (((lastbyte << 8) ^ index) % HASHSIZE)

static Byte *StrChr = NULL;
static Word *StrNxt = NULL,
           *StrHsh = NULL,
           NumStrings;

/* used in the main routines */
typedef struct {
   Word LocalScreenWidth,
        LocalScreenHeight;
   Byte GlobalColorTableSize : 3,
        SortFlag             : 1,
        ColorResolution      : 3,
        GlobalColorTableFlag : 1;
   Byte BackgroundColorIndex;
   Byte PixelAspectRatio;
} ScreenDescriptor;

typedef struct {
   Byte Separator;
   Word LeftPosition,
        TopPosition;
   Word Width,
        Height;
   Byte LocalColorTableSize : 3,
        Reserved            : 2,
        SortFlag            : 1,
        InterlaceFlag       : 1,
        LocalColorTableFlag : 1;
} ImageDescriptor;

static int  BitsPrPrimColor,    /* bits pr primary color */
           NumColors;          /* number of colors in color table */
static int  TransparentColorIndex=(-1); /* (added by j.forkosh) */
static Byte *ColorTable = NULL;
static Word ScreenHeight,
           ScreenWidth,
           ImageHeight,
           ImageWidth,
           ImageLeft,
           ImageTop,
           RelPixX, RelPixY;   /* used by InputByte() -function */
static int  (*GetPixel)(int x, int y);



/**************************************************************************
*                                                                        *
*                   P R I V A T E    F U N C T I O N S                   *
*                                                                        *
**************************************************************************/

/*========================================================================*
=                         Routines to do file IO                         =
*========================================================================*/

/*-------------------------------------------------------------------------
*
*  NAME          Create
*
*  DESCRIPTION   Creates a new file, and enables referencing using the
*                global variable OutFile. This variable is only used
*                by these IO-functions, making it relatively simple to
*                rewrite file IO.
*
*  INPUT         filename
*                        name of file to create,
*                        or NULL for stdout,
*                        or if *filename='\000' then it's the address of
*                           a memory buffer to which gif will be written
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error opening the file
*/
static int
Create(const char *filename)
{
   OutBuffer = NULL;                           /* (added by j.forkosh) */
   isCloseOutFile = 0;                         /* " */
   gifSize = 0;                                /* " */
   if ( filename == NULL )                     /* " */
     { OutFile = stdout;                       /* " */
       /*OutFile = fdopen(STDOUT_FILENO,"wb");*/ /* " doesn't work, */
       #ifdef WINDOWS                          /* "   so instead... */
         #ifdef HAVE_SETMODE                   /* "   try to use setmode()*/
           if ( setmode ( fileno (stdout), O_BINARY) /* to  set stdout */
           == -1 ) ; /* handle error */        /* " to binary mode */
         #else                                 /* " setmode not available */
           #if 1                               /* " */
             freopen ("CON", "wb", stdout);    /* " freopen stdout binary */
           #else                               /* " */
             stdout = fdopen (STDOUT_FILENO, "wb"); /*fdopen stdout binary*/
           #endif                              /* " */
         #endif                                /* " */
       #endif                                  /* " */
     }                                         /* " */
   else                                        /* " */
     if ( *filename != '\000' )                /* " */
       { if ((OutFile = fopen(filename, "wb")) == NULL)
           return GIF_ERRCREATE;
         isCloseOutFile = 1;                   /* (added by j.forkosh) */
         if ( iscachecontenttype )             /* " cache headers in file */
           if ( *contenttype != '\000' )       /* " have headers in buffer*/
             fputs(contenttype,OutFile); }     /* " write buffered headers*/
     else                                      /* " */
       OutBuffer = (Byte *)filename;           /* " */
   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          Write
*
*  DESCRIPTION   Output bytes to the current OutFile.
*
*  INPUT         buf     pointer to buffer to write
*                len     number of bytes to write
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
Write(const void *buf, unsigned len)
{
   if ( OutBuffer == NULL )                    /* (added by j.forkosh) */
     { if (fwrite(buf, sizeof(Byte), len, OutFile) < len)
         return GIF_ERRWRITE; }
   else                                        /* (added by j.forkosh) */
     { if ( gifSize+len <= maxgifSize )        /* " */
         memcpy(OutBuffer+gifSize,buf,len); }  /* " */
   gifSize += len;                             /* " */
   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteByte
*
*  DESCRIPTION   Output one byte to the current OutFile.
*
*  INPUT         b       byte to write
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
WriteByte(Byte b)
{
   if ( OutBuffer == NULL )                    /* (added by j.forkosh) */
     { if (putc(b, OutFile) == EOF)
         return GIF_ERRWRITE; }
   else                                        /* (added by j.forkosh) */
     { if ( gifSize < maxgifSize )             /* " */
         OutBuffer[gifSize] = b; }             /* " */
   gifSize++;                                  /* " */
   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteWord
*
*  DESCRIPTION   Output one word (2 bytes with byte-swapping, like on
*                the IBM PC) to the current OutFile.
*
*  INPUT         w       word to write
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
WriteWord(Word w)
{
   if ( OutBuffer == NULL )                    /* (added by j.forkosh) */
     { if (putc(w & 0xFF, OutFile) == EOF)
         return GIF_ERRWRITE;
       if (putc((w >> 8), OutFile) == EOF)
         return GIF_ERRWRITE; }
   else                                        /* (added by j.forkosh) */
     if ( gifSize+1 < maxgifSize )             /* " */
       { OutBuffer[gifSize] = (Byte)(w & 0xFF);  /* " */
         OutBuffer[gifSize+1] = (Byte)(w >> 8); }  /* " */
   gifSize += 2;                               /* " */
   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          Close
*
*  DESCRIPTION   Close current OutFile.
*/
static void
Close(void)
{
   if ( isCloseOutFile )                       /* (added by j.forkosh) */
     fclose(OutFile);
   OutBuffer = NULL;                           /* (added by j.forkosh) */
   isCloseOutFile = 0;                         /* " */
}





/*========================================================================*
=                                                                        =
=                      Routines to write a bit-file                      =
=                                                                        =
*========================================================================*/

/*-------------------------------------------------------------------------
*
*  NAME          InitBitFile
*
*  DESCRIPTION   Initiate for using a bitfile. All output is sent to
*                  the current OutFile using the I/O-routines above.
*/
static void
InitBitFile(void)
{
   Buffer[Index = 0] = 0;
   BitsLeft = 8;
}



/*-------------------------------------------------------------------------
*
*  NAME          ResetOutBitFile
*
*  DESCRIPTION   Tidy up after using a bitfile
*
*  RETURNS       0 - OK, -1 - error
*/
static int
ResetOutBitFile(void)
{
   Byte numbytes;

   /* how much is in the buffer? */
   numbytes = Index + (BitsLeft == 8 ? 0 : 1);

   /* write whatever is in the buffer to the file */
   if (numbytes) {
       if (WriteByte(numbytes) != GIF_OK)
           return -1;

       if (Write(Buffer, numbytes) != GIF_OK)
           return -1;

       Buffer[Index = 0] = 0;
       BitsLeft = 8;
   }
   return 0;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteBits
*
*  DESCRIPTION   Put the given number of bits to the outfile.
*
*  INPUT         bits    bits to write from (right justified)
*                numbits number of bits to write
*
*  RETURNS       bits written, or -1 on error.
*
*/
static int
WriteBits(int bits, int numbits)
{
   int  bitswritten = 0;
   Byte numbytes = 255;

   do {
       /* if the buffer is full, write it */
       if ((Index == 254 && !BitsLeft) || Index > 254) {
           if (WriteByte(numbytes) != GIF_OK)
               return -1;

           if (Write(Buffer, numbytes) != GIF_OK)
               return -1;

           Buffer[Index = 0] = 0;
           BitsLeft = 8;
       }

       /* now take care of the two specialcases */
       if (numbits <= BitsLeft) {
           Buffer[Index] |= (bits & ((1 << numbits) - 1)) << (8 - BitsLeft);
           bitswritten += numbits;
           BitsLeft -= numbits;
           numbits = 0;
       } else {
           Buffer[Index] |= (bits & ((1 << BitsLeft) - 1)) << (8 - BitsLeft);
           bitswritten += BitsLeft;
           bits >>= BitsLeft;
           numbits -= BitsLeft;

           Buffer[++Index] = 0;
           BitsLeft = 8;
       }
   } while (numbits);

   return bitswritten;
}



/*========================================================================*
=                Routines to maintain an LZW-string table                =
*========================================================================*/

/*-------------------------------------------------------------------------
*
*  NAME          FreeStrtab
*
*  DESCRIPTION   Free arrays used in string table routines
*/
static void
FreeStrtab(void)
{
   if (StrHsh) {
       free(StrHsh);
       StrHsh = NULL;
   }
   if (StrNxt) {
       free(StrNxt);
       StrNxt = NULL;
   }
   if (StrChr) {
       free(StrChr);
       StrChr = NULL;
   }
}



/*-------------------------------------------------------------------------
*
*  NAME          AllocStrtab
*
*  DESCRIPTION   Allocate arrays used in string table routines
*
*  RETURNS       GIF_OK     - OK
*                GIF_OUTMEM - Out of memory
*/
static int
AllocStrtab(void)
{
   /* just in case */
   FreeStrtab();

   if ((StrChr = (Byte *) malloc(MAXSTR * sizeof(Byte))) == 0) {
       FreeStrtab();
       return GIF_OUTMEM;
   }
   if ((StrNxt = (Word *) malloc(MAXSTR * sizeof(Word))) == 0) {
       FreeStrtab();
       return GIF_OUTMEM;
   }
   if ((StrHsh = (Word *) malloc(HASHSIZE * sizeof(Word))) == 0) {
       FreeStrtab();
       return GIF_OUTMEM;
   }
   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          AddCharString
*
*  DESCRIPTION   Add a string consisting of the string of index plus
*                the byte b.
*
*                If a string of length 1 is wanted, the index should
*                be 0xFFFF.
*
*  INPUT         index   index to first part of string, or 0xFFFF is
*                        only 1 byte is wanted
*                b       last byte in new string
*
*  RETURNS       Index to new string, or 0xFFFF if no more room
*/
static Word
AddCharString(Word index, Byte b)
{
   Word hshidx;

   /* check if there is more room */
   if (NumStrings >= MAXSTR)
       return 0xFFFF;

   /* search the string table until a free position is found */
   hshidx = HASH(index, b);
   while (StrHsh[hshidx] != 0xFFFF)
       hshidx = (hshidx + HASHSTEP) % HASHSIZE;

   /* insert new string */
   StrHsh[hshidx] = NumStrings;
   StrChr[NumStrings] = b;
   StrNxt[NumStrings] = (index != 0xFFFF) ? index : NEXT_FIRST;

   return NumStrings++;
}



/*-------------------------------------------------------------------------
*
*  NAME          FindCharString
*
*  DESCRIPTION   Find index of string consisting of the string of index
*                plus the byte b.
*
*                If a string of length 1 is wanted, the index should
*                be 0xFFFF.
*
*  INPUT         index   index to first part of string, or 0xFFFF is
*                        only 1 byte is wanted
*                b       last byte in string
*
*  RETURNS       Index to string, or 0xFFFF if not found
*/
static Word
FindCharString(Word index, Byte b)
{
   Word hshidx, nxtidx;

   /* check if index is 0xFFFF. in that case we need only return b,
    * since all one-character strings has their bytevalue as their
    * index */
   if (index == 0xFFFF)
       return b;

   /* search the string table until the string is found, or we find
    * HASH_FREE. in that case the string does not exist. */
   hshidx = HASH(index, b);
   while ((nxtidx = StrHsh[hshidx]) != 0xFFFF) {
       if (StrNxt[nxtidx] == index && StrChr[nxtidx] == b)
           return nxtidx;
       hshidx = (hshidx + HASHSTEP) % HASHSIZE;
   }

   /* no match is found */
   return 0xFFFF;
}



/*-------------------------------------------------------------------------
*
*  NAME          ClearStrtab
*
*  DESCRIPTION   Mark the entire table as free, enter the 2**codesize
*                one-byte strings, and reserve the RES_CODES reserved
*                codes.
*
*  INPUT         codesize
*                        number of bits to encode one pixel
*/
static void
ClearStrtab(int codesize)
{
   int q, w;
   Word *wp;

   /* no strings currently in the table */
   NumStrings = 0;

   /* mark entire hashtable as free */
   wp = StrHsh;
   for (q = 0; q < HASHSIZE; q++)
       *wp++ = HASH_FREE;

   /* insert 2**codesize one-character strings, and reserved codes */
   w = (1 << codesize) + RES_CODES;
   for (q = 0; q < w; q++)
       AddCharString(0xFFFF, q);
}



/*========================================================================*
=                        LZW compression routine                         =
*========================================================================*/

/*-------------------------------------------------------------------------
*
*  NAME          LZW_Compress
*
*  DESCRIPTION   Perform LZW compression as specified in the
*                GIF-standard.
*
*  INPUT         codesize
*                         number of bits needed to represent
*                         one pixelvalue.
*                inputbyte
*                         function that fetches each byte to compress.
*                         must return -1 when no more bytes.
*
*  RETURNS       GIF_OK     - OK
*                GIF_OUTMEM - Out of memory
*/
static int
LZW_Compress(int codesize, int (*inputbyte)(void))
{
   register int c;
   register Word index;
   int  clearcode, endofinfo, numbits, limit, errcode;
   Word prefix = 0xFFFF;

   /* set up the given outfile */
   InitBitFile();

   /* set up variables and tables */
   clearcode = 1 << codesize;
   endofinfo = clearcode + 1;

   numbits = codesize + 1;
   limit = (1 << numbits) - 1;

   if ((errcode = AllocStrtab()) != GIF_OK)
       return errcode;
   ClearStrtab(codesize);

   /* first send a code telling the unpacker to clear the stringtable */
   WriteBits(clearcode, numbits);

   /* pack image */
   while ((c = inputbyte()) != -1) {
       /* now perform the packing. check if the prefix + the new
        *  character is a string that exists in the table */
       if ((index = FindCharString(prefix, c)) != 0xFFFF) {
           /* the string exists in the table. make this string the
            * new prefix.  */
           prefix = index;
       } else {
           /* the string does not exist in the table. first write
            * code of the old prefix to the file. */
           WriteBits(prefix, numbits);

           /* add the new string (the prefix + the new character) to
            * the stringtable */
           if (AddCharString(prefix, c) > limit) {
               if (++numbits > 12) {
                   WriteBits(clearcode, numbits - 1);
                   ClearStrtab(codesize);
                   numbits = codesize + 1;
               }
               limit = (1 << numbits) - 1;
           }

           /* set prefix to a string containing only the character
            * read. since all possible one-character strings exists
            * int the table, there's no need to check if it is found. */
           prefix = c;
       }
   }

   /* end of info is reached. write last prefix. */
   if (prefix != 0xFFFF)
       WriteBits(prefix, numbits);

   /* erite end of info -mark, flush the buffer, and tidy up */
   WriteBits(endofinfo, numbits);
   ResetOutBitFile();
   FreeStrtab();

   return GIF_OK;
}



/*========================================================================*
=                              Other routines                            =
*========================================================================*/

/*-------------------------------------------------------------------------
*
*  NAME          BitsNeeded
*
*  DESCRIPTION   Calculates number of bits needed to store numbers
*                between 0 and n - 1
*
*  INPUT         n       number of numbers to store (0 to n - 1)
*
*  RETURNS       Number of bits needed
*/
static int
BitsNeeded(Word n)
{
   int ret = 1;

   if (!n--)
       return 0;
   while (n >>= 1)
       ++ret;
   return ret;
}



/*-------------------------------------------------------------------------
*
*  NAME          InputByte
*
*  DESCRIPTION   Get next pixel from image. Called by the
*                LZW_Compress()-function
*
*  RETURNS       Next pixelvalue, or -1 if no more pixels
*/
static int
InputByte(void)
{
   int ret;

   if (RelPixY >= ImageHeight)
       return -1;
   ret = GetPixel(ImageLeft + RelPixX, ImageTop + RelPixY);
   if (++RelPixX >= ImageWidth) {
       RelPixX = 0;
       ++RelPixY;
   }
   return ret;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteScreenDescriptor
*
*  DESCRIPTION   Output a screen descriptor to the current GIF-file
*
*  INPUT         sd      pointer to screen descriptor to output
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
WriteScreenDescriptor(ScreenDescriptor *sd)
{
   Byte tmp;

   if (WriteWord(sd->LocalScreenWidth) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteWord(sd->LocalScreenHeight) != GIF_OK)
       return GIF_ERRWRITE;
   tmp = (sd->GlobalColorTableFlag << 7)
         | (sd->ColorResolution << 4)
         | (sd->SortFlag << 3)
         | sd->GlobalColorTableSize;
   if (WriteByte(tmp) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteByte(sd->BackgroundColorIndex) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteByte(sd->PixelAspectRatio) != GIF_OK)
       return GIF_ERRWRITE;

   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteTransparentColorIndex (added by j.forkosh)
*
*  DESCRIPTION   Output a graphic extension block setting transparent
*                colormap index
*
*  INPUT         colornum       colormap index of color to be transparent
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
WriteTransparentColorIndex(int colornum)
{
   if ( colornum < 0 ) return GIF_OK;          /*no transparent color set*/
   if (WriteByte((Byte)(0x21)) != GIF_OK)      /*magic:Extension Introducer*/
       return GIF_ERRWRITE;
   if (WriteByte((Byte)(0xf9)) != GIF_OK)     /*magic:Graphic Control Label*/
       return GIF_ERRWRITE;
   if (WriteByte((Byte)(4)) != GIF_OK)         /* #bytes in block */
       return GIF_ERRWRITE;
   if (WriteByte((Byte)(1)) != GIF_OK)        /*transparent index indicator*/
       return GIF_ERRWRITE;
   if (WriteWord((Word)(0)) != GIF_OK)         /* delay time */
       return GIF_ERRWRITE;
   if (WriteByte((Byte)(colornum)) != GIF_OK)  /* transparent color index */
       return GIF_ERRWRITE;
   if (WriteByte((Byte)(0)) != GIF_OK)        /* terminator */
       return GIF_ERRWRITE;

   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          WriteImageDescriptor
*
*  DESCRIPTION   Output an image descriptor to the current GIF-file
*
*  INPUT         id      pointer to image descriptor to output
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to the file
*/
static int
WriteImageDescriptor(ImageDescriptor *id)
{
   Byte tmp;

   if (WriteByte(id->Separator) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteWord(id->LeftPosition) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteWord(id->TopPosition) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteWord(id->Width) != GIF_OK)
       return GIF_ERRWRITE;
   if (WriteWord(id->Height) != GIF_OK)
       return GIF_ERRWRITE;
   tmp = (id->LocalColorTableFlag << 7)
         | (id->InterlaceFlag << 6)
         | (id->SortFlag << 5)
         | (id->Reserved << 3)
         | id->LocalColorTableSize;
   if (WriteByte(tmp) != GIF_OK)
       return GIF_ERRWRITE;

   return GIF_OK;
}



/**************************************************************************
*                                                                        *
*                    P U B L I C    F U N C T I O N S                    *
*                                                                        *
**************************************************************************/

/*-------------------------------------------------------------------------
*
*  NAME          GIF_Create
*
*  DESCRIPTION   Create a GIF-file, and write headers for both screen
*                and image.
*
*  INPUT         filename
*                        name of file to create (including extension)
*                width   number of horisontal pixels on screen
*                height  number of vertical pixels on screen
*                numcolors
*                        number of colors in the colormaps
*                colorres
*                        color resolution. Number of bits for each
*                        primary color
*
*  RETURNS       GIF_OK        - OK
*                GIF_ERRCREATE - Couldn't create file
*                GIF_ERRWRITE  - Error writing to the file
*                GIF_OUTMEM    - Out of memory allocating color table
*/
int
GIF_Create(const char *filename, int width, int height,
          int numcolors, int colorres)
{
   int q, tabsize;
   Byte *bp;
   ScreenDescriptor SD;

   /* initiate variables for new GIF-file */
   NumColors = numcolors ? (1 << BitsNeeded(numcolors)) : 0;
   BitsPrPrimColor = colorres;
   ScreenHeight = height;
   ScreenWidth = width;

   /* create file specified */
   if (Create(filename) != GIF_OK)
       return GIF_ERRCREATE;

   /* write GIF signature */
   if ((Write("GIF87a", 6)) != GIF_OK)
       return GIF_ERRWRITE;

   /* initiate and write screen descriptor */
   SD.LocalScreenWidth = width;
   SD.LocalScreenHeight = height;
   if (NumColors) {
       SD.GlobalColorTableSize = BitsNeeded(NumColors) - 1;
       SD.GlobalColorTableFlag = 1;
   } else {
       SD.GlobalColorTableSize = 0;
       SD.GlobalColorTableFlag = 0;
   }
   SD.SortFlag = 0;
   SD.ColorResolution = colorres - 1;
   SD.BackgroundColorIndex = 0;
   SD.PixelAspectRatio = 0;
   if (WriteScreenDescriptor(&SD) != GIF_OK)
       return GIF_ERRWRITE;

   /* allocate color table */
   if (ColorTable) {
       free(ColorTable);
       ColorTable = NULL;
   }
   if (NumColors) {
       tabsize = NumColors * 3;
       if ((ColorTable = (Byte *) malloc(tabsize * sizeof(Byte))) == NULL)
           return GIF_OUTMEM;
       else {
           bp = ColorTable;
           for (q = 0; q < tabsize; q++)
               *bp++ = 0;
       }
   }
   return 0;
}



/*-------------------------------------------------------------------------
*
*  NAME          GIF_SetColor
*
*  DESCRIPTION   Set red, green and blue components of one of the
*                colors. The color components are all in the range
*                [0, (1 << BitsPrPrimColor) - 1]
*
*  INPUT         colornum
*                        color number to set. [0, NumColors - 1]
*                red     red component of color
*                green   green component of color
*                blue    blue component of color
*/
void
GIF_SetColor(int colornum, int red, int green, int blue)
{
   long maxcolor;
   Byte *p;

   maxcolor = (1L << BitsPrPrimColor) - 1L;
   p = ColorTable + colornum * 3;
   *p++ = (Byte) ((red * 255L) / maxcolor);
   *p++ = (Byte) ((green * 255L) / maxcolor);
   *p++ = (Byte) ((blue * 255L) / maxcolor);
}



/*-------------------------------------------------------------------------
*
*  NAME          GIF_SetTransparent (added by j.forkosh)
*
*  DESCRIPTION   Set colormap index of color to be transparent
*
*  INPUT         colornum
*                        color number to set transparent. [0, NumColors - 1]
*/
void
GIF_SetTransparent(int colornum)
{
   TransparentColorIndex = colornum;
}



/*-------------------------------------------------------------------------
*
*  NAME          GIF_CompressImage
*
*  DESCRIPTION   Compress an image into the GIF-file previousely
*                created using GIF_Create(). All color values should
*                have been specified before this function is called.
*
*                The pixels are retrieved using a user defined callback
*                function. This function should accept two parameters,
*                x and y, specifying which pixel to retrieve. The pixel
*                values sent to this function are as follows:
*
*                    x : [ImageLeft, ImageLeft + ImageWidth - 1]
*                    y : [ImageTop, ImageTop + ImageHeight - 1]
*
*                The function should return the pixel value for the
*                point given, in the interval [0, NumColors - 1]
*
*  INPUT         left    screen-relative leftmost pixel x-coordinate
*                        of the image
*                top     screen-relative uppermost pixel y-coordinate
*                        of the image
*                width   width of the image, or -1 if as wide as
*                        the screen
*                height  height of the image, or -1 if as high as
*                        the screen
*                getpixel
*                        address of user defined callback function.
*                        (see above)
*
*  RETURNS       GIF_OK       - OK
*                GIF_OUTMEM   - Out of memory
*                GIF_ERRWRITE - Error writing to the file
*/
int
GIF_CompressImage(int left, int top, int width, int height,
                 int (*getpixel)(int x, int y))
{
   int codesize, errcode;
   ImageDescriptor ID;

   if (width < 0) {
       width = ScreenWidth;
       left = 0;
   }
   if (height < 0) {
       height = ScreenHeight;
       top = 0;
   }
   if (left < 0)
       left = 0;
   if (top < 0)
       top = 0;

   /* write global colortable if any */
   if (NumColors)
       if ((Write(ColorTable, NumColors * 3)) != GIF_OK)
           return GIF_ERRWRITE;

   /* write graphic extension block with transparent color index */
   if ( TransparentColorIndex >= 0 )     /* (added by j.forkosh) */
     if ( WriteTransparentColorIndex(TransparentColorIndex)
     !=   GIF_OK ) return GIF_ERRWRITE;

   /* initiate and write image descriptor */
   ID.Separator = ',';
   ID.LeftPosition = ImageLeft = left;
   ID.TopPosition = ImageTop = top;
   ID.Width = ImageWidth = width;
   ID.Height = ImageHeight = height;
   ID.LocalColorTableSize = 0;
   ID.Reserved = 0;
   ID.SortFlag = 0;
   ID.InterlaceFlag = 0;
   ID.LocalColorTableFlag = 0;

   if (WriteImageDescriptor(&ID) != GIF_OK)
       return GIF_ERRWRITE;

   /* write code size */
   codesize = BitsNeeded(NumColors);
   if (codesize == 1)
       ++codesize;
   if (WriteByte(codesize) != GIF_OK)
       return GIF_ERRWRITE;

   /* perform compression */
   RelPixX = RelPixY = 0;
   GetPixel = getpixel;
   if ((errcode = LZW_Compress(codesize, InputByte)) != GIF_OK)
       return errcode;

   /* write terminating 0-byte */
   if (WriteByte(0) != GIF_OK)
       return GIF_ERRWRITE;

   return GIF_OK;
}



/*-------------------------------------------------------------------------
*
*  NAME          GIF_Close
*
*  DESCRIPTION   Close the GIF-file
*
*  RETURNS       GIF_OK       - OK
*                GIF_ERRWRITE - Error writing to file
*/
int
GIF_Close(void)
{
   ImageDescriptor ID;

   /* initiate and write ending image descriptor */
   ID.Separator = ';';
   ID.LeftPosition = 0;        /* (added by j.forkosh) */
   ID.TopPosition = 0;         /* " initialize entire ID structure */
   ID.Width = 0;               /* " and ditto for other ID.x=0; below */
   ID.Height = 0;
   ID.LocalColorTableSize = 0;
   ID.Reserved = 0;
   ID.SortFlag = 0;
   ID.InterlaceFlag = 0;
   ID.LocalColorTableFlag = 0;

   if (WriteImageDescriptor(&ID) != GIF_OK)
       return GIF_ERRWRITE;

   /* close file */
   Close();

   /* release color table */
   if (ColorTable) {
       free(ColorTable);
       ColorTable = NULL;
   }

   return GIF_OK;
}
/* --- end-of-file gifsave.c --- */