
// gfxgif.cpp
//
// Copyright (c) 1995 by Toshiaki Tsuji, all rights reserved.

#include "stdgfx.h"
#include "gfxgif.h"

#define HASH_SIZE 5011
#define HASH_BITS 4
#define CAPACITY  ((long)1<<12)
#define MAXBITS 12

struct SEQ_INTERLACED
  {
    INT Start;
    INT Skip;
  }; // End of SEQ_INTERLACED

SEQ_INTERLACED InterlacedData[4] =
  {
    { 0, 8 },
    { 4, 8 },
    { 2, 4 },
    { 1, 2 }
  }; // End of InterlacedData

GIFFILETOOL::GIFFILETOOL ( FILEHANDLE f ) : IMAGEFILETOOL ( f )
  {
    CodeTable = NULL;
    CodeStack = NULL;
    Prefix = NULL;
    Suffix = NULL;
    DataBlock = NULL;
  } // End of Constructor for GIFFILETOOL

GIFFILETOOL::~GIFFILETOOL ()
  {
    DeInit ();
  } // End of Destructor for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadHeader ()
  {
    BOOLEAN Ok;

    File.Seek ( fp, 0, FROM_BEGIN );
    Ok = File.Read ( fp, &GIFHeader, sizeof(GIFHEADER) );
    if (!Ok)
      return FAILURE;

    if (strncmp(GIFHeader.ID,"GIF",3)!=0)
      return FAILURE;
    if ((strncmp(GIFHeader.Version,"87a",3)!=0)&&
          (strncmp(GIFHeader.Version,"89a",3)!=0))
      return FAILURE;

    return SUCCESS;
  } // End of ReadHeader for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadScrDSC ()
  {
    BOOLEAN Ok;

    Ok = File.Read ( fp, &ScrDSC, 7 );
    if (!Ok)
      return FAILURE;

    #if defined (__MSBFIRST__)
      SwapWord ( (WORD*)&ScrDSC.Width );
      SwapWord ( (WORD*)&ScrDSC.Height );
    #endif
    return SUCCESS;
  } // End of ReadScrDSC for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadGCT ( RGBPALETTE *Pal )
  {
    INT NumColors;
    RGBCOLOR *Entry;

    NumColors = ScrDSC.NumColors ();
    if (Pal==NULL)
      {
        RGBCOLOR Table[256];
        File.Read ( fp, Table, sizeof(RGBCOLOR)*NumColors );
        return SUCCESS;
      } // End if

    Entry = Pal->GetEntry ();

    INT i;
    for (i=0;i<NumColors;i++)
      {
        Entry[i].Red = (BYTE)File.GetCh ( fp );
        Entry[i].Green = (BYTE)File.GetCh ( fp );
        Entry[i].Blue = (BYTE)File.GetCh ( fp );
      } // End for
    return SUCCESS;
  } // End of ReadGCT for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadImgDSC ()
  {
    BOOLEAN Ok;

    File.Seek ( fp, -1, FROM_CURRENT );
    Ok = File.Read ( fp, &(ImgDSC.ID), sizeof(BYTE) );
    if (!Ok)
      return FAILURE;
    Ok = File.Read ( fp, &(ImgDSC.xLeft), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Read ( fp, &(ImgDSC.yLeft), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Read ( fp, &(ImgDSC.Width), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Read ( fp, &(ImgDSC.Height), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Read ( fp, &(ImgDSC.Flags), sizeof(BYTE) );
    if (!Ok)
      return FAILURE;

    #if defined (__MSBFIRST__)
      SwapWord ( (WORD*)&ImgDSC.xLeft );
      SwapWord ( (WORD*)&ImgDSC.yLeft );
      SwapWord ( (WORD*)&ImgDSC.Width );
      SwapWord ( (WORD*)&ImgDSC.Height );
    #endif

    return SUCCESS;
  } // End of ReadImgDSC for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadLCT ( RGBPALETTE *Pal )
  {
    INT NumColors;
    RGBCOLOR *Entry;

    NumColors = ImgDSC.NumColors ();

    if (Pal==NULL)
      {
        RGBCOLOR Table[256];
        File.Read ( fp, Table, sizeof(RGBCOLOR)*NumColors );
        return SUCCESS;
      } // End if

    Entry = Pal->GetEntry ();
    INT i;
    for (i=0;i<NumColors;i++)
      {
        Entry[i].Red = (BYTE)File.GetCh ( fp );
        Entry[i].Green = (BYTE)File.GetCh ( fp );
        Entry[i].Blue = (BYTE)File.GetCh ( fp );
      } // End for
    return SUCCESS;
  } // End of ReadLCT for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadImage ( IMAGE *Image, RGBPALETTE *Pal )
  {
    BOOLEAN Ok;

    Ok = ReadImgDSC ();
    if (!Ok)
      return FAILURE;

    if (ImgDSC.IsLCT())
      {
        Ok = ReadLCT ( Pal );
        if (!Ok)
          return FAILURE;
      } // End if

    Ok = Image->Create ( IMAGE_8BIT, ImgDSC.Width, ImgDSC.Height );
    if (!Ok)
      return FAILURE;

    Ok = Decode ( Image );
    if (!Ok)
      return FAILURE;

    return SUCCESS;
  } // End of ReadImage for GIFFILETOOL

BOOLEAN GIFFILETOOL::Decode ( IMAGE *Image )
  {
    INT i;
    BOOLEAN Ok;

    GetCodeSize ();
    InitDecoding ();

    StackPtr = 0;
    NumBytes = 0;
    ByteCount = 0;
    CurByteMask = 256;

    CurCode = GetCode ();
    while (CurCode==ClearCode)
      CurCode = GetCode ();

    PreCode = CurCode;
    FirstCh = PreCode;
    PushCode ( PreCode );

    INT Row;
    INT Pass = 0;

    if (ImgDSC.IsInterlaced())
      Row = InterlacedData[Pass].Start;
    else
      Row = 0;

    for (i=0;i<Image->GetHeight();i++)
      {
        Ok = ReadRow ( Image, Row );
        if (!Ok)
          return FAILURE;

        if (ImgDSC.IsInterlaced())
          {
            Row += InterlacedData[Pass].Skip;
            if (Row>=Image->GetHeight())
              {
                Pass++;
                Row = InterlacedData[Pass].Start;
              } // End if
          } // End if
        else
          Row++;
      } // End for

    return SUCCESS;
  } // End of Decode for GIFFILETOOL

BOOLEAN GIFFILETOOL::ReadRow ( IMAGE *Image, INT Row )
  {
    DWORD Offset;
    DWORD Wd;
    BYTE *Buffer;

    Buffer = Image->SetOffset ( 0, Row );

    Wd = Image->GetWidth();
    Offset = 0;

    // Place any unplaced code from previous to buffer
    while ((Offset<Wd)&&(StackPtr>0))
      {
        Buffer[Offset] = (BYTE)PopCode ();
        Offset++;
      } // End while

    SHORT Index;

    while (Offset<Wd)
      {
        CurCode = GetCode ();
        if (CurCode==ClearCode)
          {
            ClearTable ();
            if (CurBits>=MAXBITS)
              {
                CurBits = MinBits;
                MaxCode = (SHORT)((1<<CurBits));
              } // End if
            PreCode = GetCode ();
            PushCode ( PreCode );
          } // End if
        else if (CurCode==EndCode)
          {
            break;
          } // End else if
        else
          {
            if (FindStr(CurCode))
              {
                Index = CurCode;
                PushCode ( Suffix[Index] );
                Index = Prefix[Index];

                while (Index!=NULLCODE)
                  {
                    PushCode ( Suffix[Index] );
                    Index = Prefix[Index];
                  } // End while
                FirstCh = GetFirstCh ();
              } // End if
            else
              {
                PushCode ( FirstCh );
                Index = PreCode;
                PushCode ( Suffix[Index] );
                Index = Prefix[Index];

                while (Index!=NULLCODE)
                  {
                    PushCode ( Suffix[Index] );
                    Index = Prefix[Index];
                  } // End while

                FirstCh = GetFirstCh ();
              } // End else

            AddNewString ( PreCode, FirstCh );
            PreCode = CurCode;

            if (NextAvailCode>=MaxCode)
              {
                if (CurBits<MAXBITS)
                  {
                    CurBits++;
                    MaxCode = (SHORT)(1<<CurBits);
                  } // End if
              } // End if
          } // End else

        while ((Offset<Wd)&&(StackPtr>0))
          {
            Buffer[Offset] = (BYTE)PopCode ();
            Offset++;
          } // End while
      } // End while

    return SUCCESS;
  } // End of ReadRow for GIFFILETOOL

void GIFFILETOOL::AddNewString ( SHORT Pre, SHORT New )
  {
    Prefix[NextAvailCode] = Pre;
    Suffix[NextAvailCode] = New;
    NextAvailCode++;
  } // End of AddNewString for GIFFILETOOL

SHORT GIFFILETOOL::GetFirstCh ()
  {
    if (StackPtr>0)
      return CodeStack[StackPtr-1];
    return -1;
  } // End of GetFirstCh for GIFFILETOOL

SHORT GIFFILETOOL::GetCode ()
  {
    INT Code;
    INT BitMask;
    INT i;

    BitMask = 1;
    Code = 0;

    for (i=0;i<CurBits;i++)
      {
        if (CurByteMask>128)
          CurByte = GetByte ();
        if (CurByte&CurByteMask)
          Code |= BitMask;

        BitMask <<= 1;
        CurByteMask <<= 1;
      } // End for
    return (SHORT)Code;
  } // End of GetCode for GIFFILETOOL

BYTE GIFFILETOOL::GetByte ()
  {
    ByteCount++;
    if (ByteCount>=NumBytes)
      {
        BYTE Byte;
        File.Read ( fp, &Byte, sizeof(BYTE) );
        NumBytes = Byte;
        if (NumBytes!=0)
          File.Read ( fp, DataBlock, sizeof(BYTE)*NumBytes );
        ByteCount = 0;
      } // End if

    CurByteMask = 1;
    return DataBlock[ByteCount];
  } // End of GetByte for GIFFILETOOL

void GIFFILETOOL::PushCode ( SHORT Code )
  {
    if (StackPtr==HASHSIZE)
      exit(-1);
    CodeStack[StackPtr] = Code;
    StackPtr++;
  } // End of PushCode for GIFFILETOOL

SHORT GIFFILETOOL::PopCode ( )
  {
    StackPtr--;
    return CodeStack[StackPtr];
  } // End of PopCode for GIFFILETOOL

BOOLEAN GIFFILETOOL::InitDecoding ()
  {
    DeInit ();
    CodeTable = new SHORT [HASHSIZE];
    CodeStack = new SHORT [HASHSIZE];
    Prefix = new SHORT [HASHSIZE];
    Suffix = new SHORT [HASHSIZE];
    DataBlock = new BYTE [256];

    if ((CodeTable==NULL)||
         (CodeStack==NULL)||
          (Prefix==NULL)||
           (Suffix==NULL)||
            (DataBlock==NULL))
      return FAILURE;

    ClearTable ();

    return SUCCESS;
  } // End of InitDecoding for GIFFILETOOL

void GIFFILETOOL::DeInit ()
  {
    if (CodeTable!=NULL)
      delete CodeTable;

    if (CodeStack!=NULL)
      delete CodeStack;

    if (Prefix!=NULL)
      delete Prefix;

    if (Suffix!=NULL)
      delete Suffix;

    if (DataBlock!=NULL)
      delete DataBlock;

    CodeTable = NULL;
    CodeStack = NULL;
    Prefix = NULL;
    Suffix = NULL;
    DataBlock = NULL;
  } // End of DeInitDecoding for GIFFILETOOL

BOOLEAN GIFFILETOOL::FindStr ( SHORT Code )
  {
    if ((Code>=0)&&(Code<NextAvailCode))
      return TRUE;
    return FALSE;
  } // End of FindStr for GIFFILETOOL

void GIFFILETOOL::ClearTable ()
  {
    INT i;

    for (i=0;i<RootSize;i++)
      {
        Prefix[i] = NULLCODE;
        Suffix[i] = (SHORT)i;
      } // End for
    NextAvailCode = (SHORT)(RootSize + 2);
  } // End of ClearTable for GIFFILETOOL

INT GIFFILETOOL::GetCodeSize ()
  {
    BYTE Size;

    File.Read ( fp, &Size, sizeof(BYTE) );
    MinBits = (SHORT)(Size+1);
    CurBits = MinBits;
    RootSize = (SHORT)(1<<Size);
    ClearCode = RootSize;
    EndCode = (SHORT)(RootSize+1);
    NextAvailCode = (SHORT)(RootSize+2);
    MaxCode = (SHORT)(1<<CurBits);
    return Size;
  } // End of GetCodeSize for GIFFILETOOL

BOOLEAN GIFFILETOOL::LoadImage ( IMAGE *Image, RGBPALETTE *Pal )
  {
    BOOLEAN Ok;

    if (Image==NULL)
      return FAILURE;

    Ok = ReadHeader ();
    if (!Ok)
      return FAILURE;

    Ok = ReadScrDSC ();
    if (!Ok)
      return FAILURE;

    if (ScrDSC.IsGCT())
      {
        Ok = ReadGCT ( Pal );
        if (!Ok)
          return FAILURE;
      } // End if

    SHORT Type;

    Type = 1;

    while (Type>0)
      {
        Type = (SHORT)File.GetCh ( fp );
        switch (Type)
          {
            case 0x2C :  // Image Discriptor
              Ok = ReadImage ( Image, Pal );
              if (!Ok)
                break;
              return SUCCESS;
            default :
              break;
          } // End switch
      } // End while

    DeInit ();
    return Ok;
  } // End of LoadImage for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteHeader ()
  {
    BOOLEAN Ok;

    strcpy (GIFHeader.ID,"GIF");
    strcpy (GIFHeader.Version,"87a");

    File.Seek ( fp, 0, FROM_BEGIN );
    Ok = File.Write ( fp, &GIFHeader, sizeof(GIFHEADER) );
    if (!Ok)
      return FAILURE;

    return SUCCESS;
  } // End of WriteHeader for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteScrDSC ( INT Wd, INT Ht )
  {
    DWORD Size;

    ScrDSC.Width = (SHORT)Wd;
    ScrDSC.Height = (SHORT)Ht;

    #if defined (__MSBFIRST__)
      SwapWord ( (WORD*)&ScrDSC.Width );
      SwapWord ( (WORD*)&ScrDSC.Height );
    #endif

    ScrDSC.Flags = 0;
    ScrDSC.Flags |= scdGCT;     // Set global color table
    ScrDSC.Flags |= (8 - 1)<<4; // Set the Color Depth
    ScrDSC.Flags &= ~scdSORT;   // Set non-sorted
    ScrDSC.Flags |= 7;          // Set 256 colors

    ScrDSC.BGColor = 0;

    Size = File.Write ( fp, &ScrDSC, 7 );
    if (Size!=7)
      return FAILURE;
    return SUCCESS;
  } // End of WriteScrDSC for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteGCT ( RGBPALETTE *Pal )
  {
    RGBCOLOR *Entry;

    if (Pal==NULL)
      return FAILURE;

    Entry = Pal->GetEntry ();

    INT i;
    for (i=0;i<256;i++)
      {
        File.PutCh ( fp, Entry[i].Red );
        File.PutCh ( fp, Entry[i].Green );
        File.PutCh ( fp, Entry[i].Blue );
      } // End for

    return SUCCESS;
  } // End of WriteGCT for GIFFILETOOL

BOOLEAN GIFFILETOOL::InitEncoding ()
  {
    DeInit ();
    CodeTable = new SHORT [HASHSIZE];
    CodeStack = new SHORT [HASHSIZE];
    Prefix = new SHORT [HASHSIZE];
    Suffix = new SHORT [HASHSIZE];
    DataBlock = new BYTE [256];

    if ((CodeTable==NULL)||
         (CodeStack==NULL)||
          (Prefix==NULL)||
           (Suffix==NULL)||
            (DataBlock==NULL))
      return FAILURE;

    InitTable ();

    return SUCCESS;
  } // End of InitEncoding for GIFFILETOOL

INT GIFFILETOOL::FindStr ( INT Pre, INT Cur )
  {
    INT Di,Index;
    Index = (Cur<<HASH_BITS) ^ Pre;
    if (Index==0)
      Di = 1;
    else
      Di = CAPACITY - Index;

    BOOLEAN Done=FALSE;

         while (!Done)
      {
        if (CodeTable[Index]==-1) // This string is not found
          {
            Done = TRUE;
            break;
          } // End if

        if ((Prefix[Index]==Pre)&&(Suffix[Index]==Cur)) // Found
          {
            Done = TRUE;
            break;
                         } // End if

        Index -= Di;
        if (Index<0)
          {
            while (Index<0)
              Index += CAPACITY;
            Di ++;
          } // End if
      } // End while

         if (Done)
                {}

         return Index;
  } // End of FindStr for GIFFILETOOL

void GIFFILETOOL::InitTable ()
  {
    INT i;

    for (i=0;i<HASH_SIZE;i++)
      {
        CodeTable[i] = -1;
        Suffix[i] = -1;
        Prefix[i] = -1;
      } // End for

    CurBits = MinBits;
    NextAvailCode = (SHORT)(RootSize+2);
    MaxCode = (SHORT)(1<<CurBits);
  } // End of InitTable for GIFFILETOOL

void GIFFILETOOL::PutCodeSize ( BYTE CodeSize )
  {
    File.Write ( fp, &CodeSize, 1 );
  } // End of PutCodeSize for GIFFILETOOL

void GIFFILETOOL::PutCode ( SHORT Code )
  {
    INT BitMask;
    INT i;

    BitMask = 1;

    for (i=0;i<CurBits;i++)
      {
        if (Code&BitMask)
          CurByte |= CurByteMask;
        BitMask <<= 1;
        CurByteMask <<= 1;
        if (CurByteMask>128)
          {
            PutByte ();
          } // End if
      } // End for
  } // End of PutCode for GIFFILETOOL

void GIFFILETOOL::PutByte ()
  {
    BYTE Count;
    DataBlock[NumBytes++] = (BYTE)CurByte;
    CurByte = 0;
    CurByteMask = 1;

    if (NumBytes>=255)
      {
        Count = (BYTE)NumBytes;
        File.Write ( fp, &Count, 1 );
        File.Write ( fp, DataBlock, NumBytes );
        NumBytes = 0;
      } // End if
  } // End of PutByte for GIFFILETOOL

void GIFFILETOOL::AddNewString ( INT Pre, INT Cur, INT Index )
  {
    CodeTable[Index] = NextAvailCode;
    NextAvailCode++;
    Prefix[Index] = (SHORT)Pre;
    Suffix[Index] = (SHORT)Cur;
  } // End of AddNewString for GIFFILETOOL

void GIFFILETOOL::FlushAll ()
  {
    BYTE Count;
    Count = (BYTE)NumBytes;
    File.Write ( fp, &Count, 1 );
    File.Write ( fp, DataBlock, NumBytes );
  } // End of FlushAll for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteRow ( IMAGE *Image, INT Row, INT Sx, INT Wd )
  {
    BYTE *Buffer;

    Buffer = Image->SetOffset ( Sx, Row );
    if (Buffer==NULL)
      return FAILURE;

    INT Offset;
    INT Index;
    Offset = 0;

    if (Row==0)
      PreCode = Buffer[Offset++];

    while (Offset<Wd)
      {
        CurCode = Buffer[Offset];
        Offset++;
        Index = FindStr(PreCode,CurCode);
        if (CodeTable[Index]!=-1)
          {
            PreCode = CodeTable[Index];
          } // End if
        else
          {
            AddNewString ( PreCode, CurCode, Index );
            PutCode ( PreCode );
            PreCode = CurCode;
            if (CodeTable[Index]>MaxCode-1)
              {
                if (CurBits>=MAXBITS)
                  {
                    PutCode ( ClearCode ); // Clear Code
                    InitTable ();
                  } // End if
                else
                  {
                    CurBits++;
                    MaxCode = (SHORT)(1<<CurBits);
                  } // End else
              } // End if
          } // End else
      } // End while
    return SUCCESS;
  } // End of WriteRow for GIFFILETOOL

BOOLEAN GIFFILETOOL::Encode ( IMAGE *Image, INT Sx, INT Sy, INT Wd, INT Ht )
  {
    MinBits = 8+1;
    RootSize = (1<<8);
    ClearCode = RootSize;
    EndCode = (SHORT)(RootSize+1);
    CurBits = MinBits;
    NextAvailCode = (SHORT)(RootSize+2);
    MaxCode = (SHORT)((1<<CurBits));

    PutCodeSize ( 8 );
    InitEncoding ();

    NumBytes = 0;
    CurByteMask = 1;
    CurByte = 0;

    PutCode ( ClearCode ); // Clear Code

    INT i;
    BOOLEAN Ok;

    for (i=Sy;i<Sy+Ht;i++)
      {
        Ok = WriteRow ( Image, i, Sx, Wd );
        if (!Ok)
          return FAILURE;
      } // End for

    PutCode ( PreCode );
    PutCode ( EndCode ); // Put End Code
    FlushAll ();

    return SUCCESS;
  } // End of Encode for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteImgDSC ( INT Wd, INT Ht )
  {
    BOOLEAN Ok;

    ImgDSC.ID = 0x2C;
    ImgDSC.xLeft = 0;
    ImgDSC.yLeft = 0;
    ImgDSC.Width = (SHORT)Wd;
    ImgDSC.Height = (SHORT)Ht;
    ImgDSC.Flags = 0;
    ImgDSC.Flags |= 7;

    #if defined (__MSBFIRST__)
      SwapWord ( (WORD*)&ImgDSC.xLeft );
      SwapWord ( (WORD*)&ImgDSC.yLeft );
      SwapWord ( (WORD*)&ImgDSC.Width );
      SwapWord ( (WORD*)&ImgDSC.Height );
    #endif

    Ok = File.Write ( fp, &(ImgDSC.ID), sizeof(BYTE) );
    if (!Ok)
      return FAILURE;
    Ok = File.Write ( fp, &(ImgDSC.xLeft), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Write ( fp, &(ImgDSC.yLeft), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Write ( fp, &(ImgDSC.Width), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Write ( fp, &(ImgDSC.Height), sizeof(SHORT) );
    if (!Ok)
      return FAILURE;
    Ok = File.Write ( fp, &(ImgDSC.Flags), sizeof(BYTE) );
    if (!Ok)
      return FAILURE;
    return SUCCESS;
  } // End of WriteImgDSC for GIFFILETOOL

BOOLEAN GIFFILETOOL::WriteImage ( IMAGE *Image, INT Sx, INT Sy, INT Wd, INT Ht )
  {
    BOOLEAN Ok;

    Ok = WriteImgDSC ( Wd, Ht );
    if (!Ok)
     return FAILURE;

    Ok = Encode ( Image, Sx, Sy, Wd, Ht );
    if (!Ok)
     return FAILURE;

    BYTE End = 0;

    File.Write ( fp, &End, 1 );

    return SUCCESS;
  } // End of WriteImage for GIFFILETOOL

BOOLEAN GIFFILETOOL::SaveImage ( IMAGE *Image, LONG Sx, LONG Sy,
                                 LONG Wd, LONG Ht, RGBPALETTE *Pal )
  {
    if (Image==NULL)
      return FAILURE;

    BOOLEAN Ok;

    Ok = WriteHeader ();
    if (!Ok)
      return FAILURE;

    Ok = WriteScrDSC ( Wd, Ht );
    if (!Ok)
      return FAILURE;

    Ok = WriteGCT ( Pal );
    if (!Ok)
      return FAILURE;

    Ok = WriteImage ( Image, Sx, Sy, Wd, Ht );
    if (!Ok)
      return FAILURE;

    BYTE EndTrailer = 0x3B;

    File.Write ( fp, &EndTrailer, 1 );

    return SUCCESS;
  } // End of SaveImage for GIFFILETOOL
