
/*
**
**  $VER: dispatch.c 2.2 (13.4.98)
**  gifanim.datatype 2.2
**
**  Dispatch routine for a DataTypes class
**
**  Written 1997/1998 by Roland 'Gizzy' Mainz
**  Original example source from David N. Junod
**
*/

/* main includes */
#include "classbase.h"
#include "classdata.h"

/* ansi includes */
#include <limits.h>

/*****************************************************************************/
/* debugging */

#if 0
void kprintf( STRPTR, ... );
#define D( x ) x
#else
#define D( x )
#endif

/*****************************************************************************/

/* decoder related local prototypes */
static BOOL             ReadColorMap( struct ClassBase *, struct GIFAnimInstData *, UWORD, struct ColorRegister * );
static void             DoExtension( struct ClassBase *, Object *, struct GIFAnimInstData *, TEXT );
static int              GetDataBlock( struct ClassBase *, struct GIFAnimInstData *, UBYTE * );
static int              GetCode( struct ClassBase *, struct GIFAnimInstData *, int, BOOL );
static int              LWZReadByte( struct ClassBase *, struct GIFAnimInstData *, BOOL, int );
static void             ReadImage( struct ClassBase *, struct GIFAnimInstData *, UBYTE *, UWORD, UWORD, UWORD, UWORD, UWORD, BOOL, BOOL, UWORD );
static void             WriteDeltaPixelArray8Fast( struct BitMap *, UBYTE *, UBYTE * );
static int              getbase2( int );
static BOOL             ReadOK( struct ClassBase *, struct GIFDecoder *, void *, ULONG );

/*****************************************************************************/

/* local prototypes */
static                 BOOL                 ScanFrames( struct ClassBase *, Object * );
static                 struct FrameNode    *AllocFrameNode( struct ClassBase *, APTR );
static                 struct FrameNode    *FindFrameNode( struct MinList *, ULONG );
static                 void                 FreeFrameNodeResources( struct ClassBase *, struct GIFAnimInstData * );
static                 struct BitMap       *AllocFrameBitMap( struct ClassBase *, struct GIFAnimInstData * );
static                 void                 FreeFrameBitMap( struct ClassBase *, struct GIFAnimInstData *, struct BitMap * );
static                 struct BitMap       *AllocBitMapPooled( struct ClassBase *, ULONG, ULONG, ULONG, APTR );
static                 struct FrameNode    *GetPrevFrameNode( struct FrameNode *, ULONG );
static                 void                 AttachSample( struct ClassBase *, struct GIFAnimInstData * );

/*****************************************************************************/

/* Create "gifanim.datatype" BOOPSI class */
struct IClass *initClass( struct ClassBase *cb )
{
    struct IClass *cl;

    /* Create our class... */
    if( cl = MakeClass( GIFANIMDTCLASS, ANIMATIONDTCLASS, NULL, (ULONG)sizeof( struct GIFAnimInstData ), 0UL ) )
    {
#define DTSTACKSIZE (16384UL)
      cl -> cl_Dispatcher . h_Entry    = (HOOKFUNC)StackSwapDispatch; /* see stackswap.c */
      cl -> cl_Dispatcher . h_SubEntry = (HOOKFUNC)Dispatch;          /* see stackswap.c */
      cl -> cl_Dispatcher . h_Data     = (APTR)DTSTACKSIZE;           /* see stackswap.c */
      cl -> cl_UserData                = (ULONG)cb;

      AddClass( cl );
    }

    return( cl );
}


/* class dispatcher */
DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase        *cb = (struct ClassBase *)(cl -> cl_UserData);
    struct GIFAnimInstData  *gaid;
    ULONG                    retval = 0UL;

    switch( msg -> MethodID )
    {
/****** gifanim.datatype/OM_NEW **********************************************
*
*    NAME
*        OM_NEW -- Create a gifanim.datatype object.
*
*    FUNCTION
*        The OM_NEW method is used to create an instance of the
*        gifanim.datatype class.  This method is passed to the superclass
*        first. After this, gifanim.datatype parses the prefs file and makes
*        a scan through the data to get index information. Frame bitmaps are
*        loaded if the input stream isn't seekable, colormaps and the first
*        frame are loaded immediately.
*        If a sample was set in the prefs, it will be loaded and attached
*        to the animation.
*
*    ATTRIBUTES
*        The following attributes can be specified at creation time.
*
*        DTA_SourceType (ULONG) -- Determinates the type of DTA_Handle
*            attribute. Only DTST_FILE and DTST_RAM are supported.
*            If any other type was set in a given DTA_SourceType,
*            OM_NEW will be rejected.
*            Defaults to DTST_FILE.
*
*        DTA_Handle -- For DTST_FILE, a BPTR filehandle is expected. This
*            handle will be created by datatypesclass depeding on the DTF_#?
*            flag, which is DTF_BINARY here.  DTST_FILE, datatypesclass
*            creates a file handle from the given DTA_Name and DTA_Handle
*            (a BPTR returned by Lock).
*            A DTST_RAM (create empty object) source type requires a NULL
*            handle.
*
*    RESULT
*        If the object was created a pointer to the object is returned,
*        otherwise NULL is returned.
*
******************************************************************************
*
*/
      case OM_NEW:
      {
          struct TagItem *ti;

          /* We only support DTST_FILE or DTST_RAM as source type */
          if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
          {
            if( ((ti -> ti_Data) != DTST_FILE) && ((ti -> ti_Data) != DTST_RAM) )
            {
              SetIoErr( ERROR_OBJECT_WRONG_TYPE );

              break;
            }
          }

          if( retval = DoSuperMethodA( cl, o, msg ) )
          {
            /* Load frames... */
            if( !ScanFrames( cb, (Object *)retval ) )
            {
              /* Something went fatally wrong, dispose object */
              CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
              retval = 0UL;
            }
          }
      }
          break;

/****** gifanim.datatype/OM_DISPOSE ******************************************
*
*    NAME
*        OM_DISPOSE -- Delete a gifanim.datatype object.
*
*    FUNCTION
*        The OM_DISPOSE method is used to delete an instance of the
*        gifanim.datatype class. This method is passed to the superclass when
*        it has completed.
*        This method frees all frame nodes and their contents (bitmaps,
*        colormaps, samples etc.)
*
*    RESULT
*        The object is deleted. 0UL is returned.
*
******************************************************************************
*
*/
      case OM_DISPOSE:
      {
          LONG saved_ioerr = IoErr();

          /* Get a pointer to our object data */
          gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );

          /* Wait for any outstanding blitter usage (which may use one of our bitmaps) */
          WaitBlit();

          /* Free colormaps etc. (e.g. all resources which are NOT free'ed on DeletePool below) */
          FreeFrameNodeResources( cb, gaid );

          /* Free our key bitmap */
          FreeBitMap( (gaid -> gaid_KeyBitMap) );

          /* Delete the frame pool */
          DeletePool( (gaid -> gaid_Pool) );

          /* Close input file */
          if( gaid -> gaid_FH )
          {
            Close( (gaid -> gaid_FH) );
          }

          /* Close verbose output file */
          if( gaid -> gaid_VerboseOutput )
          {
            Close( (gaid -> gaid_VerboseOutput) );
          }

          /* Dispose object */
          DoSuperMethodA( cl, o, msg );

          /* Restore Result2 */
          SetIoErr( saved_ioerr );
      }
          break;

/* TEST TEST / Support for format change "on-the-fly" disabled here / TEST TEST  */
#ifdef COMMENTED_OUT
      case DTM_FRAMEBOX:
      {
          struct dtFrameBox *dtf = (struct dtFrameBox *)msg;

          gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );

          /* pass to superclas first */
          retval = DoSuperMethodA( cl, o, msg );

          /* Does someone tell me in what for an environment (screen) I'll be attached to ? */
          if( (dtf -> dtf_FrameFlags) & FRAMEF_SPECIFY )
          {
            if( dtf -> dtf_ContentsInfo )
            {
              if( dtf -> dtf_ContentsInfo -> fri_Screen )
              {
                struct BitMap *bm = dtf -> dtf_ContentsInfo -> fri_Screen -> RastPort . BitMap;

                /* Does we have a non-planar bitmap ? */
                if( !(GetBitMapAttr( bm, BMA_FLAGS ) & BMF_STANDARD) )
                {
                  /* I assume here that the system is able to map a 24 bit bitmap into the screen
                   * if it is deeper than 8 bit.
                   */
                  if( ((bm -> Depth) > 8UL) && ((dtf -> dtf_ContentsInfo -> fri_Dimensions . Depth) > 8UL) )
                  {
                    verbose_printf( cb, gaid, "using chunky bitmap\n" );
                  }
                }
              }
            }
          }
      }
          break;
#endif /* COMMENTED_OUT */

      case OM_UPDATE:
      {
          if( DoMethod( o, ICM_CHECKLOOP ) )
          {
            break;
          }
      }
      case OM_SET:
      {
          /* Pass the attributes to the animation class and force a refresh if we need it */
          if( retval = DoSuperMethodA( cl, o, msg ) )
          {
            /* Top instance ? */
            if( OCLASS( o ) == cl )
            {
              struct RastPort *rp;

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct opSet *)msg) -> ops_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct opSet *)msg) -> ops_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_UPDATE;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );

                /* We did a refresh... */
                retval = 0UL;
              }
            }
          }
      }
          break;

/****** gifanim.datatype/DTM_WRITE *******************************************
*
*    NAME
*        DTM_WRITE -- Save data
*
*    FUNCTION
*        This method saves the object's contents to disk.
*
*        If dtw_Mode is DTWM_IFF, the method is passed unchanged to the
*        superclass, animation.datatype, which writes a single IFF ILBM
*        picture.
*
*        If dtw_mode is DTWM_RAW, the object saved an GIF Animation stream 
*        to the filehandle given, starting with the current frame until
*        the end is reached.
*        The sequence saved can be controlled by the ADTA_Frame, ADTA_Frames
*        and ADTA_FrameIncrement attributes (see TAGS section below).
*
*    TAGS
*        When writing the local ("raw") format, GIF Animation, the following
*        attributes are recognized:
*
*        ADTA_Frame (ULONG) - start frame, saving starts here.
*            Defaults to the current frame displayed.
*
*        ADTA_Frames (ULONG) - the number of frames to be saved,
*            Defaults to (max_num_of_frames - curr_frame).
*
*        ADTA_FrameIncrement (ULONG) - frame increment when saving.
*            Defaults to 1, which means: "jump to next frame".
*
*    NOTE
*        - Any sound attached to the animation will NOT be saved.
*
*        - A CTRL-D signal to the writing process aborts the save.
*
*    RESULT
*        Returns 0 for failure (IoErr() returns result2), non-zero
*        for success.
*
******************************************************************************
*
*/
      case DTM_WRITE:
      {
          struct dtWrite *dtw;

          dtw = (struct dtWrite *)msg;

          /* Local data format requested ?... */
          if( (dtw -> dtw_Mode) == DTWM_RAW )
          {
            retval = SaveGIFAnim( cb, cl, o, dtw );
          }
          else
          {
            /* Pass msg to superclass (which writes a single frame as an IFF ILBM picture)... */
            retval = DoSuperMethodA( cl, o, msg );
          }
      }
          break;


/****** gifanim.datatype/ADTM_LOADFRAME *****************************************
*
*    NAME
*        ADTM_LOADFRAME -- Load frame
*
*    FUNCTION
*        The ADTM_LOADFRAME method is used to obtain the bitmap and timing
*        data of the animation.
*        The given timestamp will be used to find a matching timestamp
*        in the internal FrameNode list. If it was found, the corresponding
*        timing, bitmap and colormap data are stored into the struct
*        adtFrame. If the bitmap wasn't loaded, this method attempts to
*        load it from disk.
*
*    RESULT
*        the bitmap ptr if a bitmap was found,
*        0 (and result2 with the reason).
*
******************************************************************************
*
*/
      case ADTM_LOADFRAME:
      {
          struct adtFrame  *alf         = (struct adtFrame *)msg;
          struct adtFrame   freeframe;
          struct FrameNode *fn;
          LONG              error       = 0L;

          gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );

          ObtainSemaphore( (&(gaid -> gaid_SigSem)) );

          /* Like "realloc": Free any given frame (the free is done AFTER the load to
           * avoid that a frame which is loaded will be freed and then loaded again...
           */
          if( alf -> alf_UserData )
          {
            /* Copy message contents that we can call ADTM_UNLOADFRAME below */
            freeframe = *alf;
            alf -> alf_UserData = NULL; /* "freeframe" now owns the frame data to free ! */
          }
          else
          {
            /* No data to free... */
            freeframe . alf_UserData = NULL;
          }

          /* Find frame by timestamp */
          if( fn = FindFrameNode( (&(gaid -> gaid_FrameList)), (alf -> alf_TimeStamp) ) )
          {
            /* Load bitmaps only if we don't cache the whole anim and
             * if we have a filehandle to load from (an empty object created using DTST_RAM won't have this)...
             */
            if( (!(gaid -> gaid_LoadAll)) && (gaid -> gaid_FH) )
            {
              /* If no bitmap is loaded, load it... */
              if( !(fn -> fn_BitMap) )
              {
                ULONG animwidth  = (ULONG)(gaid -> gaid_PaddedWidth),
                      animheight = (ULONG)(gaid -> gaid_Height);

                /* Allocate array for chunkypixel data */
                if( fn -> fn_ChunkyMap = (UBYTE *)AllocVecPooled( cb, (gaid -> gaid_Pool), ((animwidth * animheight) + 256) ) )
                {
                  /* Get a clean background to avoid that rubbish shows througth transparent parts */
                  memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );

                  if( fn -> fn_BitMap = AllocFrameBitMap( cb, gaid ) )
                  {
                    struct FrameNode *worknode = fn;
                    struct FrameNode *prevnode = NULL;
                    ULONG             rollback = 0UL;
                    UBYTE            *deltamap = NULL;

                    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

                    /* See if we need a rollback (if TRUE, we copy (below) the previous chunkymap into our
                     * current chunkymap as background. If Left/Top != 0 or transparent colors are present,
                     * parts of this previous image will occur).
                     */
                    switch( fn -> fn_GIF89aDisposal )
                    {
                      case GIF89A_DISPOSE_NODISPOSE:
                      case GIF89A_DISPOSE_RESTOREPREVIOUS:
                      {
                          do
                          {
                            worknode = GetPrevFrameNode( worknode, 1UL );

                            rollback++;
                          } while( ((worknode -> fn_ChunkyMap) == NULL) && ((worknode -> fn_TimeStamp) != 0UL) );
                      }
                          break;
                    }

                    if( ((worknode -> fn_ChunkyMap) == NULL) && ((worknode -> fn_TimeStamp) == 0UL) )
                    {
                      error_printf( cb, gaid, "first frame without bitmap ... !\n" );
                    }

                    do
                    {
                      ULONG current = rollback;

                      worknode = fn;

                      while( current-- )
                      {
                        worknode = GetPrevFrameNode( worknode, 1UL );
                      }

                      if( (worknode -> fn_ChunkyMap) && (worknode != fn) )
                      {
                        prevnode = worknode;
                      }
                      else
                      {
                        if( Seek( (gaid -> gaid_FH), ((worknode -> fn_BMOffset) - (gaid -> gaid_CurrFilePos)), OFFSET_CURRENT ) != (-1L) )
                        {
                          if( gifdec -> file_buffer = AllocVec( ((worknode -> fn_BMSize) + 16UL), MEMF_PUBLIC ) )
                          {
                            BOOL   useGlobalColormap;
                            UWORD  bitPixel;
                            UBYTE  buf[ 16 ];

                            /* Init buffer */
                            gifdec -> buffer     = gifdec -> file_buffer;
                            gifdec -> buffersize = worknode -> fn_BMSize;
                            gifdec -> which_fh   = WHICHFH_BUFFER;

                            /* Fill buffer */
                            if( Read( (gifdec -> file), (gifdec -> buffer), (gifdec -> buffersize) ) == (gifdec -> buffersize) )
                            {
                              if( !ReadOK( cb, gifdec, buf, 9 ) )
                              {
                                error = IoErr();
                                D( kprintf( "couldn't read left/top/width/height\n" ) );
                              }

                              useGlobalColormap = !BitSet( buf[ 8 ], LOCALCOLORMAP );

                              bitPixel = 1 << ((buf[ 8 ] & 0x07) + 1);

                              /* disposal method */
                              switch( worknode -> fn_GIF89aDisposal )
                              {
                                case GIF89A_DISPOSE_NOP:
                                {
                                  /* Background not transparent ? */
                                  if( ((worknode -> fn_GIF89aTransparent) == ~0U) ||
                                      ((worknode -> fn_GIF89aTransparent) != 0U) )
                                  {
                                    /* Restore to color 0 */
                                    memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );
                                  }
                                }
                                    break;

                                case GIF89A_DISPOSE_NODISPOSE:
                                {
                                    /* do not dispose prev image */

                                    /* Check if we have a prevnode link to the previous image.
                                     * If this is NULL, we assume that our chunkymap already contain
                                     * the previous image
                                     */
                                    if( prevnode )
                                    {
                                      CopyMem( (prevnode -> fn_ChunkyMap), (fn -> fn_ChunkyMap), (animwidth * animheight) );
#ifdef DELTAWPA8
                                      CopyBitMap( cb, (fn -> fn_BitMap), (prevnode -> fn_BitMap), animwidth, animheight );
                                      deltamap = prevnode -> fn_ChunkyMap;
#endif /* DELTAWPA8 */
                                    }
                                    else
                                    {
#ifdef DELTAWPA8
                                      deltamap = NULL;
#endif /* DELTAWPA8 */
                                    }
                                }
                                    break;

                                case GIF89A_DISPOSE_RESTOREBACKGROUND:
                                {
                                    /* Background not transparent ? */
                                    if( ((worknode -> fn_GIF89aTransparent) == ~0U) ||
                                        ((worknode -> fn_GIF89aTransparent) != (gaid -> gaid_GIFDec . GifScreen . Background)) )
                                    {
                                      /* Restore to background color */
                                      memset( (fn -> fn_ChunkyMap), (gaid -> gaid_GIFDec . GifScreen . Background), (size_t)(animwidth * animheight) );
                                    }
                                }
                                    break;

                                case GIF89A_DISPOSE_RESTOREPREVIOUS:
                                {
                                    /* restore image of previous frame */

                                    /* Check if we have a prevnode link to the previous image.
                                     * If this is NULL, we assume that our chunkymap already contain
                                     * the previous image
                                     */
                                    if( prevnode )
                                    {
                                      CopyMem( (prevnode -> fn_ChunkyMap), (fn -> fn_ChunkyMap), (animwidth * animheight) );
#ifdef DELTAWPA8
                                      CopyBitMap( cb, (fn -> fn_BitMap), (prevnode -> fn_BitMap), animwidth, animheight );
                                      deltamap = prevnode -> fn_ChunkyMap;
#endif /* DELTAWPA8 */
                                    }
                                    else
                                    {
#ifdef DELTAWPA8
                                      deltamap = NULL;
#endif /* DELTAWPA8 */
                                    }
                                }
                                    break;

                                default: /* GIF89A_DISPOSE_RESERVED4 - GIF89A_DISPOSE_RESERVED7 */
                                {
                                    error_printf( cb, gaid, "unsupported disposal method %lu\n", (ULONG)(gaid -> gaid_GIFDec . Gif89 . disposal) );
                                }
                                    break;
                              }

                              if( !useGlobalColormap )
                              {
                                /* Skip colormap (in buffer) */
                                gifdec -> buffer += (sizeof( struct ColorRegister ) * bitPixel);
                              }

                              ReadImage( cb, gaid,
                                         (fn -> fn_ChunkyMap),
                                         (UWORD)animwidth,
                                         LOHI2UINT16( buf[ 0 ], buf[ 1 ] ),
                                         LOHI2UINT16( buf[ 2 ], buf[ 3 ] ),
                                         LOHI2UINT16( buf[ 4 ], buf[ 5 ] ),
                                         LOHI2UINT16( buf[ 6 ], buf[ 7 ] ),
                                         BitSet( buf[ 8 ], INTERLACE ),
                                         FALSE,
                                         (worknode -> fn_GIF89aTransparent) );
                            }

                            FreeVec( (gifdec -> file_buffer) );
                            gifdec -> file_buffer = gifdec -> buffer = NULL;
                          }

                          /* Bump file pos */
                          gaid -> gaid_CurrFilePos = Seek( (gaid -> gaid_FH), 0L, OFFSET_CURRENT ); /* BUG: does not check for failure */
                        }
                        else
                        {
                          /* seek failed */
                          error = IoErr();
                          break;
                        }

                        prevnode = NULL; /* a previous image is now in our chunkymap,
                                          * we don't need the link anymore
                                          */
                      }
                    } while( rollback-- );

                    if( error == 0L )
                    {
                      if( fn -> fn_ChunkyMap )
                      {
                        if( gaid -> gaid_UseChunkyMap )
                        {
                          WriteRGBPixelArray8( cb, (fn -> fn_BitMap), animwidth, animheight, (fn -> fn_ColorMap), (fn -> fn_ChunkyMap) );
                        }
                        else
                        {
                          WriteDeltaPixelArray8Fast( (fn -> fn_BitMap), (fn -> fn_ChunkyMap), deltamap );
                        }
                      }
                    }
                  }
                  else
                  {
                    /* can't alloc bitmap */
                    error = ERROR_NO_FREE_STORE;
                  }
                }
                else
                {
                  /* can't alloc chunkymap */
                  error = ERROR_NO_FREE_STORE;
                }
              }
            }

            /* Store timing/context information */
            alf -> alf_Duration = fn -> fn_Duration;
            alf -> alf_Frame    = fn -> fn_Frame;
            alf -> alf_UserData = (APTR)fn;        /* Links back to this FrameNode (used by ADTM_UNLOADFRAME) */

            /* Store bitmap information */
            alf -> alf_BitMap = fn -> fn_BitMap;
            alf -> alf_CMap   = ((gaid -> gaid_UseChunkyMap)?(NULL):(fn -> fn_CMap)); /* we does not use a colormap with a direct RGB-coded bitmap */

            /* Is there a sample to play ? */
            if( fn -> fn_Sample )
            {
              /* Store sound information */
              alf -> alf_Sample       = fn -> fn_Sample;
              alf -> alf_SampleLength = fn -> fn_SampleLength;
              alf -> alf_Period       = fn -> fn_Period;
            }
            else
            {
              /* No sound */
              alf -> alf_Sample       = NULL;
              alf -> alf_SampleLength = 0UL;
              alf -> alf_Period       = 0UL;
            }

            /* Frame "in use", even for a unsuccessful result; on error
             * animation.datatype send an ADTM_UNLOADFRAME which frees
             * allocated resources and decreases the "UseCount"...
             */
            fn -> fn_UseCount++;

            /* Return bitmap ptr of possible, 0UL and error cause otherwise */
            retval = ((error)?(0UL):(ULONG)(alf -> alf_BitMap)); /* Result  */
          }
          else
          {
            /* no matching frame found */
            error = ERROR_OBJECT_NOT_FOUND;
          }

          /* Like "realloc": Free any given frame here */
          if( freeframe . alf_UserData )
          {
            freeframe . MethodID = ADTM_UNLOADFRAME;
            DoMethodA( o, (Msg)(&freeframe) );
          }

          SetIoErr( error ); /* Result2 */

          ReleaseSemaphore( (&(gaid -> gaid_SigSem)) );
      }
          break;

/****** gifanim.datatype/ADTM_UNLOADFRAME ************************************
*
*    NAME
*        ADTM_UNLOADFRAME -- Unload frame contents
*
*    FUNCTION
*        The ADTM_UNLOADFRAME method is used to release the contents of a
*        animation frame.
*
*        This method frees the bitmap data found in adtFrame.
*
*    RESULT
*        Returns always 0UL.
*
******************************************************************************
*
*/
      case ADTM_UNLOADFRAME:
      {
          struct FrameNode *fn;
          struct adtFrame  *alf;

          gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );
          alf = (struct adtFrame *)msg;

          /* Free bitmaps only if we don't cache the whole anim */
          if( (gaid -> gaid_LoadAll) == FALSE )
          {
            ObtainSemaphore( (&(gaid -> gaid_SigSem)) );

            if( fn = (struct FrameNode *)(alf -> alf_UserData) )
            {
              if( (fn -> fn_UseCount) > 0 )
              {
                fn -> fn_UseCount--;

                /* Free an existing bitmap if it isn't in use and if it is NOT the first bitmap */
                if( ((fn -> fn_UseCount) == 0) && (fn -> fn_BitMap) && (fn != (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head)) )
                {
                  FreeFrameBitMap( cb, gaid, (fn -> fn_BitMap) );
                  FreeVecPooled( cb, (gaid -> gaid_Pool), (fn -> fn_ChunkyMap) );
                  fn -> fn_BitMap    = NULL;
                  fn -> fn_ChunkyMap = NULL;
                }
              }
            }

            ReleaseSemaphore( (&(gaid -> gaid_SigSem)) );
          }

          /* The frame has been freed ! */
          alf -> alf_UserData = NULL;
      }
          break;

      /* Let the superclass handle everything else */
      default:
      {
          retval = DoSuperMethodA( cl, o, msg );
      }
          break;
    }

    return( retval );
}


static
BOOL ScanFrames( struct ClassBase *cb, Object *o )
{
    struct GIFAnimInstData *gaid    = (struct GIFAnimInstData *)INST_DATA( (cb -> cb_Lib . cl_Class), o );
    LONG                    error   = 0L;
    BOOL                    success = FALSE;

    InitSemaphore( (&(gaid -> gaid_SigSem)) );
    NewList( (struct List *)(&(gaid -> gaid_FrameList)) );

    /* Create a memory pool for frame nodes */
    if( gaid -> gaid_Pool = CreatePool( MEMF_PUBLIC, 8192UL, 8192UL ) )
    {
      BPTR                 fh;                             /* handle (file handle)      */
      ULONG                sourcetype;                     /* type of stream (either DTST_FILE or DTST_RAM) */
      ULONG                modeid /*= (ULONG)INVALID_ID*/; /* anim view mode                  */
      ULONG                animwidth,                      /* anim width                      */
                           animheight,                     /* anim height                     */
                           animdepth;                      /* anim depth                      */
      ULONG                timestamp  = 0UL;               /* timestamp                       */

      /* Prefs defaults */
      gaid -> gaid_LoadAll = TRUE;              /* The decoder is too slow to allow realtime decoding of a
                                                 * 576 * 124 * 8 GIF Image, even on a mc68040 :-((
                                                 */
      gaid -> gaid_ModeID  = (ULONG)INVALID_ID; /* no screen mode selected yet */
      gaid -> gaid_Volume  = 64UL;

      /* Read prefs */
      ReadENVPrefs( cb, gaid, NULL );

      /* Get file handle, handle type and BitMapHeader */
      if( GetDTAttrs( o, DTA_SourceType,    (&sourcetype),
                         DTA_Handle,        (&fh),
                         DTA_Name,          (&(gaid -> gaid_ProjectName)),
                         TAG_DONE ) == 3UL )
      {
        switch( sourcetype )
        {
          case DTST_FILE:
          {
              if( fh )
              {
                BPTR lock;

                if( lock = DupLockFromFH( fh ) )
                {
                  /* Set up a filehandle for disk-based loading (random loading) */
                  if( gaid -> gaid_FH = (LONG)OpenFromLock( lock ) )
                  {
                    success = TRUE;
                  }
                  else
                  {
                    /* failure */
                    UnLock( lock );
                  }
                }
              }

              /* OpenFromLock failed ? - Then open by name :-( */
              if( (gaid -> gaid_FH) == NULL )
              {
                /* Set up a filehandle for disk-based loading (random loading) */
                if( gaid -> gaid_FH = (LONG)Open( (gaid -> gaid_ProjectName), MODE_OLDFILE ) )
                {
                  success = TRUE;
                }
                else
                {
                  /* Can't open file */
                  error = IoErr();
                }
              }
          }
              break;

          case DTST_RAM: /* empty object */
          {
              success = TRUE;
          }
              break;

          default:
          {
              /* unsupported source type */
              error = ERROR_NOT_IMPLEMENTED;
          }
              break;
        }

        /* Any error ? */
        if( success )
        {
          /* Now we enter the next stage of testing... */
          success = FALSE;

          if( fh )
          {
            struct GIFDecoder    *gifdec                           = (&(gaid -> gaid_GIFDec));
            struct FrameNode     *fn;
            ULONG                 numcmaps                         = 0UL; /* number of created cmaps */
            UBYTE                 buf[ 16 ];
            struct ColorRegister  localColorMap[ MAXCOLORMAPSIZE ] = { 0 };
            struct ColorRegister  savedTransparentColor            = { 0 };
            UBYTE                 c;
            BOOL                  useGlobalColormap;
            UWORD                 bitPixel;
            UBYTE                 version[ 4 ];

            gifdec -> file                = fh;
            gifdec -> Gif89 . transparent = (UWORD)~0U; /* means: no transparent color */

            /* Read "GIF" indentifer and version */
            if( ReadOK( cb, gifdec, buf, 6 ) )
            {
              /* Is there the GIF signature ? */
              if( !strncmp( (char *)buf, "GIF", 3 ) )
              {
                strncpy( version, (char *)(buf + 3), 3 );
                version[ 3 ] = '\0';

                /* Check if we support this GIF version */
                if( (!strcmp( version, "87a" )) ||
                    (!strcmp( version, "89a" )) )
                {
                  /* Read GIF Screen */
                  if( ReadOK( cb, gifdec, buf, 7 ) )
                  {
                    struct FrameNode *prevnode = NULL;
                    UBYTE            *deltamap = NULL;

                    gifdec -> GifScreen . Width           = LOHI2UINT16( buf[ 0 ], buf[ 1 ] );
                    gifdec -> GifScreen . Height          = LOHI2UINT16( buf[ 2 ], buf[ 3 ] );
                    gifdec -> GifScreen . BitPixel        = 2 << (buf[ 4 ] & 0x07);
                    gifdec -> GifScreen . ColorResolution = (((buf[ 4 ] & 0x70) >> 3) + 1);
                    gifdec -> GifScreen . Background      = buf[ 5 ];
                    gifdec -> GifScreen . AspectRatio     = buf[ 6 ];

                                 gaid -> gaid_Width        = gifdec -> GifScreen . Width;
                    animwidth  = gaid -> gaid_PaddedWidth  = (gaid -> gaid_UseChunkyMap)?(gaid -> gaid_Width):(((gaid -> gaid_Width) + 15UL) & ~15UL); /* align for c2p-wpa (c2c does not need padding) */
                    animheight = gaid -> gaid_Height       = gifdec -> GifScreen . Height;
                    animdepth  = gaid -> gaid_Depth        = (gaid -> gaid_UseChunkyMap)?(DIRECTRGB_DEPTH):((ULONG)getbase2( (gifdec -> GifScreen . BitPixel) ));

                    /* Global Colormap ? */
                    if( BitSet( buf[ 4 ], LOCALCOLORMAP ) )
                    {
                      numcmaps++;

                      if( ReadColorMap( cb, gaid, (gifdec -> GifScreen . BitPixel), (gifdec -> GifScreen . ColorMap) ) )
                      {
                        error = IoErr();
                        D( kprintf( "error reading global colormap\n" ) );
                      }
                    }
                    else
                    {
                      /* No global colormap ? - Then the background color in the GifScreen is a NOP */
                      gifdec -> GifScreen . Background = 0U;
                    }

                    for( ;; )
                    {
                      /* Read chunk ID char */
                      if( !ReadOK( cb, gifdec, (&c), 1 ) )
                      {
                        error = IoErr();
                        D( kprintf( "EOF / read error on image data\n" ) );
                      }

                      switch( c )
                      {
                        case ';': /* GIF terminator ? */
                        {
                            goto scandone;
                        }

                        case '!': /* Extension ? */
                        {
                            if( !ReadOK( cb, gifdec, (&c), 1 ) )
                            {
                              error = IoErr();
                              D( kprintf( "OF / read error on extension function code\n" ) );
                            }

                            DoExtension( cb, o, gaid, c );
                        }
                            break;

                        case ',': /* Raster data start ? */
                        {
                            /* Create an prepare a new frame node */
                            if( fn = AllocFrameNode( cb, (gaid -> gaid_Pool) ) )
                            {
                              if( (gaid -> gaid_LoadAll) || (timestamp == 0UL) )
                              {
                                if( !(fn -> fn_BitMap = AllocFrameBitMap( cb, gaid ) ) )
                                {
                                  error = ERROR_NO_FREE_STORE;
                                }

                                /* Allocate array for chunkypixel data */
                                if( fn -> fn_ChunkyMap = (UBYTE *)AllocVecPooled( cb, (gaid -> gaid_Pool), ((animwidth * animheight) + 256) ) )
                                {
                                  /* Get a clean background to avoid that rubbish shows througth transparent parts */
                                  memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );
                                }
                                else
                                {
                                  error = ERROR_NO_FREE_STORE;
                                }
                              }

                              if( error == 0L )
                              {
                                ULONG duration;

                                /* Get position of bitmap */
                                fn -> fn_BMOffset = Seek( fh, 0L, OFFSET_CURRENT ); /* BUG: does not check for failure */

                                D( kprintf( "pos %lu\n", (fn -> fn_BMOffset) ) );

                                if( !ReadOK( cb, gifdec, buf, 9 ) )
                                {
                                  error = IoErr();
                                  D( kprintf( "couldn't read left/top/width/height\n" ) );
                                }

                                /* Local color map ? */
                                useGlobalColormap = !BitSet( buf[ 8 ], LOCALCOLORMAP );

                                /* Size of local color map */
                                bitPixel = 1 << ((buf[ 8 ] & 0x07) + 1);

                                /* Store GIF89a related attributes */
                                fn -> fn_GIF89aDisposal    = gifdec -> Gif89 . disposal;    /* Store disposal mode for current frame  */
                                fn -> fn_GIF89aTransparent = gifdec -> Gif89 . transparent; /* Store currents frame transparent color */

                                if( fn -> fn_ChunkyMap )
                                {
                                  /* disposal method */
                                  switch( fn -> fn_GIF89aDisposal )
                                  {
                                    case GIF89A_DISPOSE_NOP:
                                    {
                                        /* Background not transparent ? */
                                        if( ((fn -> fn_GIF89aTransparent) == ~0U) ||
                                            ((fn -> fn_GIF89aTransparent) != 0U) )
                                        {
                                          /* restore to color 0 */
                                          memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );
                                        }
                                    }
                                        break;

                                    case GIF89A_DISPOSE_NODISPOSE:
                                    {
                                        /* do not dispose prev image */

                                        /* If we have a previous frame, copy it  */
                                        if( prevnode )
                                        {
                                          CopyMem( (prevnode -> fn_ChunkyMap), (fn -> fn_ChunkyMap), (animwidth * animheight) );
#ifdef DELTAWPA8
                                          CopyBitMap( cb, (fn -> fn_BitMap), (prevnode -> fn_BitMap), animwidth, animheight );
                                          deltamap = prevnode -> fn_ChunkyMap;
#endif /* DELTAWPA8 */
                                        }
                                        else
                                        {
                                          /* Background not transparent ? */
                                          if( ((fn -> fn_GIF89aTransparent) == ~0U) ||
                                              ((fn -> fn_GIF89aTransparent) != 0U) )
                                          {
                                            /* restore to color 0 */
                                            memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );
                                          }
#ifdef DELTAWPA8
                                          deltamap = NULL;
#endif /* DELTAWPA8 */
                                        }
                                    }
                                        break;

                                    case GIF89A_DISPOSE_RESTOREBACKGROUND:
                                    {
                                        /* Background not transparent ? */
                                        if( ((fn -> fn_GIF89aTransparent) == ~0U) ||
                                            ((fn -> fn_GIF89aTransparent) != (gaid -> gaid_GIFDec . GifScreen . Background)) )
                                        {
                                          /* Restore to background color */
                                          memset( (fn -> fn_ChunkyMap), (gifdec -> GifScreen . Background), (size_t)(animwidth * animheight) );
                                        }
                                    }
                                        break;

                                    case GIF89A_DISPOSE_RESTOREPREVIOUS:
                                    {
                                        /* restore previous image  */

                                        /* If we have a previous frame, copy it  */
                                        if( prevnode )
                                        {
                                          CopyMem( (prevnode -> fn_ChunkyMap), (fn -> fn_ChunkyMap), (animwidth * animheight) );
#ifdef DELTAWPA8
                                          CopyBitMap( cb, (fn -> fn_BitMap), (prevnode -> fn_BitMap), animwidth, animheight );
                                          deltamap = prevnode -> fn_ChunkyMap;
#endif /* DELTAWPA8 */
                                        }
                                        else
                                        {
                                          /* restore to color 0 */
                                          memset( (fn -> fn_ChunkyMap), 0, (size_t)(animwidth * animheight) );
#ifdef DELTAWPA8
                                          deltamap = NULL;
#endif /* DELTAWPA8 */
                                        }
                                    }
                                        break;

                                    default: /* GIF89A_DISPOSE_RESERVED4 - GIF89A_DISPOSE_RESERVED7 */
                                    {
                                        error_printf( cb, gaid, "unsupported disposal method %lu\n", (ULONG)(gifdec -> Gif89 . disposal) );
                                    }
                                        break;
                                  }
                                }

                                /* Save transparent color (if we have one) */
                                if( ((fn -> fn_GIF89aTransparent) != ~0U) && (timestamp != 0UL) )
                                {
                                  savedTransparentColor = localColorMap[ (fn -> fn_GIF89aTransparent) ];
                                }

                                /* Get colormap */
                                if( useGlobalColormap )
                                {
                                  /* use global colormap and depth */
                                  bitPixel = gifdec -> GifScreen . BitPixel;
                                  memcpy( localColorMap, (gifdec -> GifScreen . ColorMap), (sizeof( struct ColorRegister ) * bitPixel) );
                                }
                                else
                                {
                                  numcmaps++;

                                  if( ReadColorMap( cb, gaid, bitPixel, localColorMap ) )
                                  {
                                    error_printf( cb, gaid, "error reading local colormap\n" );
                                    error = IoErr();
                                  }
                                }

                                /* Restore transparent color (if we have one) */
                                if( (fn -> fn_GIF89aTransparent) != ~0U )
                                {
                                  localColorMap[ (fn -> fn_GIF89aTransparent) ] = savedTransparentColor;
                                }

                                if( !(gaid -> gaid_UseChunkyMap) )
                                {
                                  /* The first palette must be moved to the object's palette */
                                  if( timestamp == 0UL )
                                  {
                                    if( !CMAP2Object( cb, o, (UBYTE *)localColorMap, (ULONG)(bitPixel * 3UL) ) )
                                    {
                                      /* can't alloc object's color table */
                                      error = ERROR_NO_FREE_STORE;
                                    }
                                  }

                                  /* Create a palette-per-frame colormap here */
                                  if( !(fn -> fn_CMap = CMAP2ColorMap( cb, (1UL << (ULONG)(gaid -> gaid_Depth)), (UBYTE *)localColorMap, (ULONG)(bitPixel * 3UL) )) )
                                  {
                                    /* can't alloc colormap */
                                    error = ERROR_NO_FREE_STORE;
                                  }
                                }

                                /* Copy colormap for 24 bit output */
                                memcpy( (void *)(fn -> fn_ColorMap), (void *)localColorMap, (size_t)(sizeof( struct ColorRegister ) * bitPixel) );

                                ReadImage( cb, gaid,
                                           (fn -> fn_ChunkyMap),
                                           (UWORD)animwidth,
                                           LOHI2UINT16( buf[ 0 ], buf[ 1 ] ),
                                           LOHI2UINT16( buf[ 2 ], buf[ 3 ] ),
                                           LOHI2UINT16( buf[ 4 ], buf[ 5 ] ),
                                           LOHI2UINT16( buf[ 6 ], buf[ 7 ] ),
                                           BitSet( buf[ 8 ], INTERLACE ),
                                           ((fn -> fn_BitMap) == NULL),
                                           (fn -> fn_GIF89aTransparent) );

                                /* Get size of bitmap (curr_pos - start_of_bm) */
                                fn -> fn_BMSize = Seek( fh, 0L, OFFSET_CURRENT ) - (fn -> fn_BMOffset); /* BUG: does not check for failure */

                                if( fn -> fn_BitMap )
                                {
                                  if( gaid -> gaid_UseChunkyMap )
                                  {
                                    WriteRGBPixelArray8( cb, (fn -> fn_BitMap), animwidth, animheight, (fn -> fn_ColorMap), (fn -> fn_ChunkyMap) );
                                  }
                                  else
                                  {
                                    WriteDeltaPixelArray8Fast( (fn -> fn_BitMap), (fn -> fn_ChunkyMap), deltamap );
                                  }
                                }

                                /* Bump timestamp... */
                                if( ((gifdec -> Gif89 . delayTime) != ~0U) &&
                                    ((gifdec -> Gif89 . delayTime) > 1U)   &&
                                    ((gifdec -> Gif89 . delayTime) < 2000U) )
                                {
                                  duration = (gifdec -> Gif89 . delayTime);
                                }
                                else
                                {
                                  duration = 0UL;
                                }

                                fn -> fn_TimeStamp = timestamp;
                                fn -> fn_Frame     = timestamp;
                                fn -> fn_Duration  = duration;

                                AddTail( (struct List *)(&(gaid -> gaid_FrameList)), (struct Node *)(&(fn -> fn_Node)) );

                                prevnode = fn;

                                /* Next frame starts at timestamp... */
                                timestamp += (fn -> fn_Duration) + 1UL;
                              }
                            }
                        }
                            break;
                            
                        case 0x00: /* padding byte ? */
                        {
                            /* Padding bytes are not part of the Compuserve documents, but... */
                            if( !(gaid -> gaid_StrictSyntax) )
                            {
                              break;
                            }
                        }

                        default: /* Not a valid raster data start character ? */
                        {
                            error_printf( cb, gaid, "invalid character 0x%02x, ignoring\n", (int)c );
                        }
                            break;
                      }

                      /* on error break */
                      if( error )
                      {
                        break;
                      }
                    }

scandone:
                    /* Any frames ? */
                    if( timestamp && (error == 0L) && numcmaps )
                    {
                      if( numcmaps == 1UL )
                      {
                        /* We only have a global colormap and no colormap changes (or a direct RGB bitmap),
                         * delete first colormap (a colormap in the first frames indicates following colormap
                         * changes)
                         */
                        struct FrameNode *firstnode = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head);

                        if( firstnode -> fn_CMap )
                        {
                          FreeColorMap( (firstnode -> fn_CMap) );
                          firstnode -> fn_CMap = NULL;
                        }
                      }
                      else
                      {
                        /* All frames must have a colormap, therefore we replicate the colormap
                         * from the previous colormap if one is missing
                         */
                        struct FrameNode *worknode,
                                         *nextnode;
                        struct ColorMap  *currcm = NULL;

                        verbose_printf( cb, gaid, "Animation has palette changes per frame\n" );

                        worknode = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head);

                        while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
                        {
                          if( worknode -> fn_CMap )
                          {
                            /* Current node contains colormap, this are the colors for the following frames... */
                            currcm = worknode -> fn_CMap;
                          }
                          else
                          {
                            if( currcm )
                            {
                              /* Copy colormap from previous one... */
                              if( !(worknode -> fn_CMap = CopyColorMap( cb, currcm )) )
                              {
                                /* Can't copy/alloc colormap */
                                error = ERROR_NO_FREE_STORE;
                              }
                            }
                            else
                            {
                              verbose_printf( cb, gaid, "scan/load: no colormap, can't copy it\n" );
                            }
                          }

                          worknode = nextnode;
                        }
                      }
                    }

                    /* Check for required information */
                    if( error == 0L )
                    {
                      /* Any frames loaded ? */
                      if( timestamp == 0UL )
                      {
                        /* not enougth frames (at least one required) */
                        error = DTERROR_NOT_ENOUGH_DATA;
                      }
                    }

                    /* Any error ? */
                    if( error == 0L )
                    {
                      struct FrameNode *firstfn = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head); /* short cut to the first FrameNode */

                      /* Alloc bitmap as key bitmap */
                      if( gaid -> gaid_UseChunkyMap )
                      {
                        gaid -> gaid_KeyBitMap = AllocBitMap( animwidth, animheight, animdepth, (BMF_SPECIALFMT | SHIFT_PIXFMT( DIRECTRGB_PIXFMT )), NULL );
                      }
                      else
                      {
                        gaid -> gaid_KeyBitMap = AllocBitMap( animwidth, animheight, animdepth, BMF_CLEAR, NULL );
                      }

                      if( gaid -> gaid_KeyBitMap )
                      {
                        if( (firstfn -> fn_BitMap) == NULL )
                        {
                          /* can't alloc first bitmap */
                          error = ERROR_NO_FREE_STORE;
                        }

                        if( error == 0L )
                        {
                          /* Copy first frame into key bitmap */
                          CopyBitMap( cb, (gaid -> gaid_KeyBitMap), (firstfn -> fn_BitMap), animwidth, animheight );

                          /* No screen mode id set by prefs ? */
                          if( (gaid -> gaid_ModeID) != (ULONG)INVALID_ID )
                          {
                            modeid = gaid -> gaid_ModeID;
                          }
                          else
                          {
                            if( gaid -> gaid_UseChunkyMap )
                            {
                              /* We don't have fixed values for cybergfx mode id's, therefore we have to ask for them */
                              if( (modeid = BestCModeIDTags( CYBRBIDTG_NominalWidth,  animwidth,
                                                             CYBRBIDTG_NominalHeight, animheight,
                                                             CYBRBIDTG_Depth,         animdepth,
                                                             TAG_DONE )) == INVALID_ID )
                              {
#if 0
                                error = 1L; /* inducate an error here :-( */
#else
                                /* Workaround for CyberGFX bug :-( */
                                if( (modeid = BestCModeIDTags( CYBRBIDTG_NominalWidth,  640UL,
                                                               CYBRBIDTG_NominalHeight, 480UL,
                                                               CYBRBIDTG_Depth,         animdepth,
                                                               TAG_DONE )) == INVALID_ID )
                                {
                                  modeid = 0UL;

                                  error_printf( cb, gaid, "'CyberGFX bug' workaround failed, too\n" );
                                }
#endif

                                error_printf( cb, gaid, "No screenmode available for %lu/%lu/%lu\n", animwidth, animheight, animdepth );
                              }
                            }
                            else
                            {
                              /* BUG: Does currently not support SUPERHIRES modes */
                              if( animwidth >= 640UL )
                              {
                                if( animheight >= 400 )
                                {
                                  modeid = HIRESLACE_KEY;
                                }
                                else
                                {
                                  modeid = HIRES_KEY;
                                }
                              }
                              else
                              {
                                if( animheight >= 400 )
                                {
                                  modeid = LORESLACE_KEY;
                                }
                                else
                                {
                                  modeid = LORES_KEY;
                                }
                              }
                            }
                          }

                          /* No fps set by prefs ? */
                          if( (gaid -> gaid_FPS) == 0UL )
                          {
                            gaid -> gaid_FPS = 100; /* defaults to 100 fps. GIF 89a delay values counts in
                                                     * 1/100 sec steps. We set the alf_Duration field
                                                     * to this value (got from the GIF 89a extension).
                                                     */
                          }

                          AttachSample( cb, gaid );

                          verbose_printf( cb, gaid, "width %lu height %lu depth %lu frames %lu fps %lu\n",
                                          animwidth,
                                          animheight,
                                          animdepth,
                                          timestamp,
                                          (gaid -> gaid_FPS) );

                          /* Set misc attributes */
                          SetDTAttrs( o, NULL, NULL,
                                      DTA_ObjName,                                       (gaid -> gaid_ProjectName),
                                      DTA_TotalHoriz,                                    animwidth,
                                      DTA_TotalVert,                                     animheight,
                                      ADTA_Width,                                        (gaid -> gaid_Width),
                                      ADTA_Height,                                       animheight,
                                      ADTA_Depth,                                        animdepth,
                                      ADTA_Frames,                                       timestamp,
                                      ADTA_FramesPerSecond,                              (gaid -> gaid_FPS),
                                      ADTA_ModeID,                                       modeid,
                                      ADTA_KeyFrame,                                     (gaid -> gaid_KeyBitMap),
                                      XTAG( (firstfn -> fn_Sample), ADTA_Sample       ), (firstfn -> fn_Sample),
                                      XTAG( (firstfn -> fn_Sample), ADTA_SampleLength ), ((firstfn -> fn_SampleLength) / ((firstfn -> fn_Duration) + 1UL)),
                                      XTAG( (firstfn -> fn_Sample), ADTA_Period       ), (firstfn -> fn_Period),
                                      XTAG( (firstfn -> fn_Sample), ADTA_Volume       ), (gaid -> gaid_Volume),
                                      TAG_DONE );

                          /* All done for now... */
                          success = TRUE;
                        }
                      }
                      else
                      {
                        /* can't alloc key bitmap */
                        error = ERROR_NO_FREE_STORE;
                      }
                    }
                  }
                  else
                  {
                    error = IoErr();
                    D( kprintf( "failed to read screen descriptor\n" ) );
                  }
                }
                else
                {
                  error = DTERROR_UNKNOWN_COMPRESSION;
                  D( kprintf( "bad version number, not '87a' or '89a'\n" ) );
                }
              }
              else
              {
                error = ERROR_OBJECT_WRONG_TYPE;
                D( kprintf( "not a GIF file\n" ) );
              }
            }
            else
            {
              error = IoErr();
              D( kprintf( "error reading magic number\n" ) );
            }

            /* Prepare decoder for dynamic frame access */
            gifdec -> file = gaid -> gaid_FH;
          }
          else
          {
            /* No file handle ? - Be sure we got a DTST_RAM sourcetype */
            if( sourcetype == DTST_RAM )
            {
              /* The object is used without any input file.
               * This "empty" object is used to run the encoder only...
               */
              success = TRUE;
            }
            else
            {
              /* No handle ! */
              error = ERROR_REQUIRED_ARG_MISSING;
            }
          }
        }
      }
      else
      {
        /* can't get required attributes from superclass */
        error = ERROR_OBJECT_WRONG_TYPE;
      }
    }
    else
    {
      /* no memory pool */
      error = ERROR_NO_FREE_STORE;
    }

    SetIoErr( error );

    return( success );
}


static
struct FrameNode *AllocFrameNode( struct ClassBase *cb, APTR pool )
{
    struct FrameNode *fn;

    if( fn = (struct FrameNode *)AllocPooled( pool, (ULONG)sizeof( struct FrameNode ) ) )
    {
      memset( fn, 0, sizeof( struct FrameNode ) );
    }

    return( fn );
}


static
struct FrameNode *FindFrameNode( struct MinList *fnl, ULONG timestamp )
{
    if( fnl )
    {
      struct FrameNode *worknode,
                       *nextnode,
                       *prevnode;

      prevnode = worknode = (struct FrameNode *)(fnl -> mlh_Head);

      while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
      {
        if( (worknode -> fn_TimeStamp) > timestamp )
        {
          return( prevnode );
        }

        prevnode = worknode;
        worknode = nextnode;
      }

      if( !IsListEmpty( ((struct List *)fnl) ) )
      {
        return( prevnode );
      }
    }

    return( NULL );
}


static
void FreeFrameNodeResources( struct ClassBase *cb, struct GIFAnimInstData *gaid )
{
    struct FrameNode *worknode;

/* The follwoing was used for debugging */
/* #define FREE_LIST_IN_REVERSE_ORDER 1 */

#ifdef FREE_LIST_IN_REVERSE_ORDER
    struct FrameNode *nextnode;

    worknode = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head);

    while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
#else
    while( worknode = (struct FrameNode *)RemTail( (struct List *)(&(gaid -> gaid_FrameList)) ) )
#endif /* FREE_LIST_IN_REVERSE_ORDER */
    {
      if( worknode -> fn_CMap )
      {
        FreeColorMap( (worknode -> fn_CMap) );
        worknode -> fn_CMap = NULL;
      }

      if( worknode -> fn_BitMap )
      {
        FreeFrameBitMap( cb, gaid, (worknode -> fn_BitMap) );
        worknode -> fn_BitMap = NULL;
      }

#ifdef FREE_LIST_IN_REVERSE_ORDER
      worknode = nextnode;
#endif /* FREE_LIST_IN_REVERSE_ORDER */
    }
}


static
struct BitMap *AllocFrameBitMap( struct ClassBase *cb, struct GIFAnimInstData *gaid )
{
    if( gaid -> gaid_UseChunkyMap )
    {
      return( AllocBitMap( (ULONG)(gaid -> gaid_PaddedWidth), (ULONG)(gaid -> gaid_Height), (ULONG)(gaid -> gaid_Depth),
                           (BMF_SPECIALFMT | SHIFT_PIXFMT( DIRECTRGB_PIXFMT )), NULL ) );
    }
    else
    {
      return( AllocBitMapPooled( cb, (ULONG)(gaid -> gaid_PaddedWidth), (ULONG)(gaid -> gaid_Height), (ULONG)(gaid -> gaid_Depth), (gaid -> gaid_Pool) ) );
    }
}


static
void FreeFrameBitMap( struct ClassBase *cb, struct GIFAnimInstData *gaid, struct BitMap *bm )
{
    if( bm )
    {
      if( gaid -> gaid_UseChunkyMap )
      {
        FreeBitMap( bm );
      }
      else
      {
        FreeVecPooled( cb, (gaid -> gaid_Pool), bm );
      }
    }
}


/* This function assumes (0UL < depth) && (depth <= 8UL) */
static
struct BitMap *AllocBitMapPooled( struct ClassBase *cb, ULONG width, ULONG height, ULONG depth, APTR pool )
{
    struct BitMap *bm;

    ULONG          planesize,
                   size;

    planesize = (ULONG)RASSIZE( width, height ) + 16UL;
    size      = ((ULONG)sizeof( struct BitMap )) + (planesize * depth) + width;

    if( bm = (struct BitMap *)AllocVecPooled( cb, pool, size ) )
    {
      UWORD    pl;
      PLANEPTR plane;

      InitBitMap( bm, depth, width, height );

      plane = (PLANEPTR)(bm + 1); /* First plane follows struct BitMap */

      /* Set up plane data */
      pl = 0U;

      /* Set up plane ptrs */
      while( pl < depth )
      {
        bm -> Planes[ pl ] = plane;

        plane = (PLANEPTR)(((UBYTE *)plane) + planesize + 8);
        pl++;
      }

      /* Clear the remaining plane ptrs */
      while( pl < 8U )
      {
        bm -> Planes[ pl ] = NULL;

        pl++;
      }
    }

    return( bm );
}


void OpenLogfile( struct ClassBase *cb, struct GIFAnimInstData *gaid )
{
    if( (gaid -> gaid_VerboseOutput) == NULL )
    {
      STRPTR confile;

      if( confile = (STRPTR)AllocVec( (((gaid -> gaid_ProjectName)?(strlen( (gaid -> gaid_ProjectName) )):(0UL)) + 100UL), MEMF_PUBLIC ) )
      {
        mysprintf( cb, confile, "CON:////GIF Anim DataType %s/auto/wait/close/inactive",
                   ((gaid -> gaid_ProjectName)?(FilePart( (gaid -> gaid_ProjectName) )):(NULL)) );

        gaid -> gaid_VerboseOutput = Open( confile, MODE_READWRITE );

        FreeVec( confile );
      }
    }
}


void error_printf( struct ClassBase *cb, struct GIFAnimInstData *gaid, STRPTR format, ... )
{
    OpenLogfile( cb, gaid );

    if( gaid -> gaid_VerboseOutput )
    {
      VFPrintf( (gaid -> gaid_VerboseOutput), format, (APTR)((&format) + 1) );
    }
}


void verbose_printf( struct ClassBase *cb, struct GIFAnimInstData *gaid, STRPTR format, ... )
{
    if( gaid -> gaid_VerboseOutput )
    {
      VFPrintf( (gaid -> gaid_VerboseOutput), format, (APTR)((&format) + 1) );
    }
}


static
void AttachSample( struct ClassBase *cb, struct GIFAnimInstData *gaid )
{
    if( gaid -> gaid_Sample )
    {
      struct FrameNode *worknode,
                       *nextnode;

      ULONG             period          = gaid -> gaid_Period;
      ULONG             samplesperframe;
      BYTE             *sample          = gaid -> gaid_Sample;

      samplesperframe = (((SysBase -> ex_EClockFrequency) * 10UL) / (period * (gaid -> gaid_FPS) * 2UL));

      if( gaid -> gaid_SamplesPerFrame )
      {
        period = (period * samplesperframe) / (gaid -> gaid_SamplesPerFrame);

        samplesperframe = gaid -> gaid_SamplesPerFrame;

        verbose_printf( cb, gaid, "period corrected from %lu to %lu to match spf=%lu with fps=%lu\n",
                        (gaid -> gaid_Period), period, samplesperframe, (gaid -> gaid_FPS) );
      }

      verbose_printf( cb, gaid, "Attching samples (sysclock %lu period %lu fps %lu length %lu samplesperframe %lu)...\n",
                      (SysBase -> ex_EClockFrequency), period, (gaid -> gaid_FPS), (gaid -> gaid_SampleLength), samplesperframe );

      worknode = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head);

      while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
      {
        worknode -> fn_Sample       = sample;
        worknode -> fn_SampleLength = samplesperframe * ((worknode -> fn_Duration) + 1UL);
        worknode -> fn_Period       = period;

        sample += worknode -> fn_SampleLength;

        /* End of sample reached ? */
        if( (ULONG)(sample - (gaid -> gaid_Sample)) > (gaid -> gaid_SampleLength) )
        {
          /* Cut last size of sample to fit */
          worknode -> fn_SampleLength -= (ULONG)(sample - (gaid -> gaid_Sample));

          break;
        }

        worknode = nextnode;
      }
    }
}


static
BOOL ReadColorMap( struct ClassBase *cb, struct GIFAnimInstData *gaid, UWORD numcolors, struct ColorRegister *color )
{
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

    return( (BOOL)(!ReadOK( cb, gifdec, color, (ULONG)(sizeof( struct ColorRegister ) * numcolors) )) );
}


static
void DoExtension( struct ClassBase *cb, Object *o, struct GIFAnimInstData *gaid, TEXT label )
{
    struct GIFDecoder *gifdec     = (&(gaid -> gaid_GIFDec));
    UBYTE              buf[ 256 ] = { 0 };
    STRPTR             str;

    switch( label )
    {
      case 0x01:              /* Plain Text Extension */
      {
          UWORD lpos,
                tpos,
                width,
                height,
                cellw,
                cellh,
                foreground,
                background;

          error_printf( cb, gaid, "'Plain text extension' not supported yet. Please send this animation to the author that"
                                  "this can be implemented\n" );

          (void)GetDataBlock( cb, gaid, buf );

          lpos       = LOHI2UINT16( buf[ 0 ], buf[ 1 ] );
          tpos       = LOHI2UINT16( buf[ 2 ], buf[ 3 ] );
          width      = LOHI2UINT16( buf[ 4 ], buf[ 5 ] );
          height     = LOHI2UINT16( buf[ 6 ], buf[ 7 ] );
          cellw      = buf[ 8 ];
          cellh      = buf[ 9 ];
          foreground = buf[ 10 ];
          background = buf[ 11 ];

          verbose_printf( cb, gaid, "Plain text: "
                                    "left %lu top %lu width %lu height %lu "
                                    "cell width %lu cell height %lu"
                                    "foreground %lu background %lu", lpos, tpos, width, height, cellw, cellh, foreground, background );

          while( GetDataBlock( cb, gaid, buf ) != 0 )
          {
#if 0
            PPM_ASSIGN( image[ ypos ][ xpos ], cmap[ CM_RED ][ v ], cmap[ CM_GREEN ][ v ], cmap[ CM_BLUE ][ v ] );

            index++;
#endif

            /* Clear buffer for next cycle */
            memset( (void *)buf, 0, sizeof( buf ) );
          }

          return;
      }

      case 0xf9:              /* Graphic Control Extension */
      {
          STRPTR fmt; /* Format string for verbose output (fmt changes if transparent color is set) */

          (void)GetDataBlock( cb, gaid, buf );

          /* Get "delta" mode (disposal of previous frame), input flag and the delay time in 1/100 sec) */
          gifdec -> Gif89 . disposal    = (buf[ 0 ] >> 2) & 0x7;
          gifdec -> Gif89 . inputFlag   = (buf[ 0 ] >> 1) & 0x1;
          gifdec -> Gif89 . delayTime   = LOHI2UINT16( buf[ 1 ], buf[ 2 ] );

          /* Any transparent color ? */
          if( buf[ 0 ] & 0x01 )
          {
            gifdec -> Gif89 . transparent = buf[ 3 ];

            fmt = "Graphic Control Extension: disposal %s (%lu)%s transparent %lu\n";
          }
          else
          {
            fmt = "Graphic Control Extension: disposal %s (%lu)%s\n";
          }

          /* Verbose output ? */
          if( gaid -> gaid_VerboseOutput )
          {
            STRPTR user_input = ((gifdec -> Gif89 . inputFlag)?(" user input requested"):(""));
            STRPTR disposal;

            switch( gifdec -> Gif89 . disposal )
            {
              case GIF89A_DISPOSE_NOP:                  disposal = "nop";                break;
              case GIF89A_DISPOSE_NODISPOSE:            disposal = "no dispose";         break;
              case GIF89A_DISPOSE_RESTOREBACKGROUND:    disposal = "restore background"; break;
              case GIF89A_DISPOSE_RESTOREPREVIOUS:      disposal = "restore previous";   break;
              default:                                  disposal = "reserved";           break;
            }

            verbose_printf( cb, gaid, fmt,
                                      disposal,
                                      (ULONG)(gifdec -> Gif89 . disposal),
                                      user_input,
                                      (gifdec -> Gif89 . transparent) );
          }

          /* Ignore remaining data... */
          while( GetDataBlock( cb, gaid, (UBYTE *)buf ) != 0 )
                  ;

          return;
      }

      case 0xfe:              /* Comment Extension */
      {
          STRPTR annotation;

          /* Get all comment extension chunks, and append them on the DTA_ObjAnnotation string we've created before */
          while( GetDataBlock( cb, gaid, buf ) != 0 )
          {
            ULONG  size;
            STRPTR oldannotation;

            buf[ 255 ] = '\0'; /* terminate explicitly */

            size = (ULONG)strlen( buf ) + 2UL;

            (void)GetDTAttrs( o, DTA_ObjAnnotation, (&oldannotation), TAG_DONE );

            if( oldannotation )
            {
              size += (ULONG)strlen( oldannotation ) + 2UL;
            }

            /* Allocate a temp buffer */
            if( annotation = (STRPTR)AllocMem( size, MEMF_ANY ) )
            {
              if( oldannotation )
              {
                strcpy( annotation, oldannotation );
              }
              else
              {
                annotation[ 0 ] = '\0'; /* terminate */
              }

              /* Append the new buffer */
              IBMPC2ISOLatin1( buf, (annotation + strlen( annotation )) );

              /* Store the comment */
              SetDTAttrs( o, NULL, NULL, DTA_ObjAnnotation, annotation, TAG_DONE );

              /* Free temp string */
              FreeMem( annotation, size );
            }

            /* Clear buffer for next cycle */
            memset( (void *)buf, 0, sizeof( buf ) );
          }

          /* After all, prompt the annotation to the user */
          (void)GetDTAttrs( o, DTA_ObjAnnotation, (&annotation), TAG_DONE );

          verbose_printf( cb, gaid, "Comment Extension: '%s'\n", annotation );

          return;
      }

      case 0xff:              /* Application Extension */
      {
          str = "Application Extension";
      }
          break;

      default:
      {
          mysprintf( cb, buf, "UNKNOWN (0x%02lx)", (long)label );
          str = buf;
      }
          break;
    }

    verbose_printf( cb, gaid, "got a '%s' extension\n", ((str)?(str):"") );

    /* skip extension data */
    while( GetDataBlock( cb, gaid, buf ) != 0 )
      ;

    return;
}


static
int GetDataBlock( struct ClassBase *cb, struct GIFAnimInstData *gaid, UBYTE *buf )
{
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));
    UBYTE              count;

    if( !ReadOK( cb, gifdec, &count, 1 ) )
    {
      error_printf( cb, gaid, "error in getting DataBlock size\n" );

      return( -1 );
    }

    gifdec -> ZeroDataBlock = (count == 0);

    if( (count != 0) && (!ReadOK( cb, gifdec, buf, (ULONG)count ) ) )
    {
      error_printf( cb, gaid, "error in reading DataBlock\n" );

      return( -1 );
    }

    return( count );
}


static
int GetCode( struct ClassBase *cb, struct GIFAnimInstData *gaid, int code_size, BOOL flag )
{
    int                i,
                       j,
                       ret;
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));
    UBYTE              count;

    if( flag )
    {
      gifdec -> GetCode . curbit  = 0;
      gifdec -> GetCode . lastbit = 0;
      gifdec -> GetCode . done    = FALSE;

      return( 0 );
    }

    if( (gifdec -> GetCode . curbit + code_size) >= gifdec -> GetCode . lastbit )
    {
      if( gifdec -> GetCode . done )
      {
        if( gifdec -> GetCode . curbit >= gifdec -> GetCode . lastbit )
          D( kprintf( "ran off the end of my bits\n" ) );

        return( -1 );
      }

      gifdec -> GetCode . buf[ 0 ] = gifdec -> GetCode . buf[ gifdec -> GetCode . last_byte - 2 ];
      gifdec -> GetCode . buf[ 1 ] = gifdec -> GetCode . buf[ gifdec -> GetCode . last_byte - 1 ];

      if( (count = GetDataBlock( cb, gaid, &gifdec -> GetCode . buf[ 2 ] ) ) == 0 )
        gifdec -> GetCode . done = TRUE;

      gifdec -> GetCode . last_byte = 2 + count;
      gifdec -> GetCode . curbit    = (gifdec -> GetCode . curbit - gifdec -> GetCode . lastbit) + 16;
      gifdec -> GetCode . lastbit   = (2 + count) * 8 ;
    }

    ret = 0;

    for( i = gifdec -> GetCode . curbit, j = 0; j < code_size ; i++, j++ )
      ret |= ((gifdec -> GetCode . buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;

    gifdec -> GetCode . curbit += code_size;

    return( ret );
}


static
int LWZReadByte( struct ClassBase *cb, struct GIFAnimInstData *gaid, BOOL flag, int input_code_size )
{
             int                code,
                                incode;
    register int                i;
             struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

    if( flag )
    {
      gifdec -> LWZReadByte . set_code_size = input_code_size;
      gifdec -> LWZReadByte . code_size     = gifdec -> LWZReadByte . set_code_size + 1;
      gifdec -> LWZReadByte . clear_code    = 1 << gifdec -> LWZReadByte . set_code_size ;
      gifdec -> LWZReadByte . end_code      = gifdec -> LWZReadByte . clear_code + 1;
      gifdec -> LWZReadByte . max_code_size = 2 * gifdec -> LWZReadByte . clear_code;
      gifdec -> LWZReadByte . max_code      = gifdec -> LWZReadByte . clear_code + 2;

      GetCode( cb, gaid, 0, TRUE );

      gifdec -> LWZReadByte . fresh = TRUE;

      /* Fill table with the codes... */
      for( i = 0 ; i < (gifdec -> LWZReadByte . clear_code) ; i++ )
      {
        gifdec -> LWZReadByte . table[ 0 ][ i ] = 0;
        gifdec -> LWZReadByte . table[ 1 ][ i ] = i;
      }

      /* ... and clear the remaining part  */
      for( ; i < (1 << MAX_LWZ_BITS) ; i++ )
      {
        gifdec -> LWZReadByte . table[ 0 ][ i ] =
          gifdec -> LWZReadByte . table[ 1 ][ 0 ] = 0;
      }

      /* Reset stack ptr */
      gifdec -> LWZReadByte . sp = gifdec -> LWZReadByte . stack;

      return( 0 );
    }
    else
    {
      if( gifdec -> LWZReadByte . fresh )
      {
        gifdec -> LWZReadByte . fresh = FALSE;

        do
        {
          gifdec -> LWZReadByte . firstcode = gifdec -> LWZReadByte . oldcode = GetCode( cb, gaid, gifdec -> LWZReadByte . code_size, FALSE );
        } while( gifdec -> LWZReadByte . firstcode == gifdec -> LWZReadByte . clear_code );

        return( gifdec -> LWZReadByte . firstcode );
      }
    }

    if( gifdec -> LWZReadByte . sp > gifdec -> LWZReadByte . stack )
    {
      return( *--gifdec -> LWZReadByte . sp );
    }

    while( (code = GetCode( cb, gaid, gifdec -> LWZReadByte . code_size, FALSE )) >= 0 )
    {
      if( code == gifdec -> LWZReadByte . clear_code )
      {
        for( i = 0 ; i < gifdec -> LWZReadByte . clear_code ; i++ )
        {
          gifdec -> LWZReadByte . table[ 0 ][ i ] = 0;
          gifdec -> LWZReadByte . table[ 1 ][ i ] = i;
        }

        for( ; i < (1 << MAX_LWZ_BITS) ; i++ )
        {
          gifdec -> LWZReadByte . table[ 0 ][ i ] =
            gifdec -> LWZReadByte . table[ 1 ][ i ] = 0;
        }

        gifdec -> LWZReadByte . code_size       = gifdec -> LWZReadByte . set_code_size + 1;
        gifdec -> LWZReadByte . max_code_size   = 2 * gifdec -> LWZReadByte . clear_code;
        gifdec -> LWZReadByte . max_code        = gifdec -> LWZReadByte . clear_code + 2;
        gifdec -> LWZReadByte . sp              = gifdec -> LWZReadByte . stack;
        gifdec -> LWZReadByte . firstcode       =
          gifdec -> LWZReadByte . oldcode       = GetCode( cb, gaid, gifdec -> LWZReadByte . code_size, FALSE );

        return( gifdec -> LWZReadByte . firstcode );
      }
      else
      {
        if( code == gifdec -> LWZReadByte . end_code )
        {
          int   count;
          UBYTE buf[ 260 ];

          if( gifdec -> ZeroDataBlock )
            return( -2 );

          while( (count = GetDataBlock( cb, gaid, buf )) > 0 )
            ;

          if( count != 0 )
            error_printf( cb, gaid, "missing EOD in data stream (common occurence)\n" );

          return( -2 );
        }
      }

      incode = code;

      if( code >= gifdec -> LWZReadByte . max_code )
      {
        *gifdec -> LWZReadByte . sp++ = gifdec -> LWZReadByte . firstcode;
        code = gifdec -> LWZReadByte . oldcode;
      }

      while( code >= gifdec -> LWZReadByte . clear_code )
      {
        *gifdec -> LWZReadByte . sp++ = gifdec -> LWZReadByte . table[ 1 ][ code ];

        if( code == gifdec -> LWZReadByte . table[ 0 ][ code ] )
          D( kprintf( "circular table entry BIG ERROR\n" ) );

        code = gifdec -> LWZReadByte . table[ 0 ][ code ];
      }

      *gifdec -> LWZReadByte . sp++ = gifdec -> LWZReadByte . firstcode = gifdec -> LWZReadByte . table[ 1 ][ code ];

      if( (code = gifdec -> LWZReadByte . max_code) < (1 << MAX_LWZ_BITS ) )
      {
        gifdec -> LWZReadByte . table[ 0 ][ code ] = gifdec -> LWZReadByte . oldcode;
        gifdec -> LWZReadByte . table[ 1 ][ code ] = gifdec -> LWZReadByte . firstcode;
        gifdec -> LWZReadByte . max_code++;

        if( (gifdec -> LWZReadByte . max_code >= gifdec -> LWZReadByte . max_code_size) && (gifdec -> LWZReadByte . max_code_size < (1 << MAX_LWZ_BITS)) )
        {
          gifdec -> LWZReadByte . max_code_size *= 2;
          gifdec -> LWZReadByte . code_size++;
        }
      }

      gifdec -> LWZReadByte . oldcode = incode;

      if( gifdec -> LWZReadByte . sp > gifdec -> LWZReadByte . stack )
      {
        return( *--gifdec -> LWZReadByte . sp );
      }
    }

    return( code );
}


static
void ReadImage( struct ClassBase *cb, struct GIFAnimInstData *gaid, UBYTE *image,
                UWORD imagewidth, UWORD left, UWORD top, UWORD len, UWORD height,
                BOOL interlace, BOOL ignore, UWORD transparent )
{
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));
    UBYTE              c;

    /* Initialize the Compression routines */
    (void)ReadOK( cb, gifdec, &c, 1 );

    /* If this is an "uninteresting picture" ignore it. */
    if( ignore )
    {
      D( kprintf( cb, gaid, "skipping gif image...\n" ) );

      /* Loop until end of raster data */
      for( ;; )
      {
        if( !ReadOK( cb, gifdec, &c, 1 ) )
          D( kprintf( "EOF / reading block byte count\n" ) );

        if( c == 0 )
        {
          D( kprintf( cb, gaid, "gif image done\n" ) );
          break;
        }

        /* Skip... */
        (void)Seek( (gifdec -> file), (long)c, OFFSET_CURRENT );
      }
    }
    else
    {
       WORD v;
      ULONG xpos    = 0UL,
            ypos    = 0UL,
            offset  = (top * imagewidth) + left,
            pass    = 0UL;

      if( LWZReadByte( cb, gaid, TRUE, c ) < 0 )
        D( kprintf( "error reading image\n" ) );

      D( kprintf( cb, gaid, "reading %lx %ld.%ld / %ld by %ld%s GIF image\n", image, left, top, len, height, interlace ? " interlaced" : "" ) );

      while( (v = LWZReadByte( cb, gaid, FALSE, c )) >= 0 )
      {
        /* Pixel transparent ? */
        if( (transparent == ~0U) ||
            (transparent != v) )
        {
          /* Store pixel */
          image[ offset + xpos ] = v;
        }

        xpos++;

        if( xpos == len )
        {
          xpos = 0UL;

          if( interlace )
          {
            switch( pass )
            {
              case 0UL:
              case 1UL: ypos += 8UL; break;
              case 2UL: ypos += 4UL; break;
              case 3UL: ypos += 2UL; break;
            }

            if( ypos >= height )
            {
              pass++;

              switch( pass )
              {
                case 1UL: ypos = 4UL;  break;
                case 2UL: ypos = 2UL;  break;
                case 3UL: ypos = 1UL;  break;
                default: goto fini;
              }
            }
          }
          else
          {
            ypos++;
          }

          offset = ((ypos + top) * imagewidth) + left;
        }

        if( ypos >= height )
          break;
      }

fini:
      if( (v = LWZReadByte( cb, gaid, FALSE, c )) >= 0 )
      {
        /* 0x00-bytes are treated here as padding bytes unless the STRICTSYNTAX option is set... */
        if( (v != 0) || (gaid -> gaid_StrictSyntax) )
        {
          verbose_printf( cb, gaid, "too much input data %ld, ignoring extra...\n", (long)v );
        }
      }
    }
}


/* got from my anim.datatype */
static
struct FrameNode *GetPrevFrameNode( struct FrameNode *currfn, ULONG interleave )
{
    struct FrameNode *worknode,
                     *prevnode;

    /* Get previous frame */
    worknode = currfn;

    while( prevnode = (struct FrameNode *)(worknode -> fn_Node . mln_Pred) )
    {
      if( (interleave-- == 0U) || ((prevnode -> fn_Node . mln_Pred) == NULL) )
      {
        break;
      }

      worknode = prevnode;
    }

    return( worknode );
}


/* WritePixelArray8 replacement by Peter McGavin (p.mcgavin@irl.cri.nz),
 * slightly adapted to fit here...
 */

static
void WriteDeltaPixelArray8Fast( struct BitMap *dest, UBYTE *source, UBYTE *prev )
{
             ULONG *plane[ 8 ] = { 0 };
    register ULONG *chunky     = (ULONG *)source, /* fetch 32 bits per cycle */
                   *prevchunky = (ULONG *)prev;
             ULONG  numcycles  = ((dest -> Rows) * (dest -> BytesPerRow)) / sizeof( ULONG ),
                    i;

    /* Copy plane ptrs */
    for( i = 0UL ; i < (dest -> Depth) ; i++ )
    {
      plane[ i ] = (ULONG *)(dest -> Planes[ i ]);
    }

    /* Fill unused planes with plane 0, which will be written last, all previous accesses
     * will be droped (assumes that a cache hides this "dummy" writes)
     */
    for( i ; i < 8UL ; i++ )
    {
      plane[ i ] = (ULONG *)(dest -> Planes[ 0 ]);
    }

#define merge( a, b, mask, shift ) \
      tmp = mask & (a ^ (b >> shift));   \
      a ^= tmp;                          \
      b ^= (tmp << shift)

    /* Check if we have to do the "delta" test */
    if( prevchunky )
    {
      /* Process bitmaps */
      for( i = 0UL ; i < numcycles ; i++ )
      {
        register ULONG b0, b1, b2, b3, b4, b5, b6, b7,
                       tmp;

        /* process 32 pixels */
        b0 = *chunky++;  b4 = *chunky++;
        b1 = *chunky++;  b5 = *chunky++;
        b2 = *chunky++;  b6 = *chunky++;
        b3 = *chunky++;  b7 = *chunky++;

        /* I use the '+' here to avoid that the compiler skips an expression.
         * WARNING: The code assumes that the code is executed in the sequence as it occurs here
         */
        if( (b0 != *prevchunky++) + (b4 != *prevchunky++) +
            (b1 != *prevchunky++) + (b5 != *prevchunky++) +
            (b2 != *prevchunky++) + (b6 != *prevchunky++) +
            (b3 != *prevchunky++) + (b7 != *prevchunky++) )
        {
          merge( b0, b2, 0x0000ffff, 16 );
          merge( b1, b3, 0x0000ffff, 16 );
          merge( b4, b6, 0x0000ffff, 16 );
          merge( b5, b7, 0x0000ffff, 16 );

          merge( b0, b1, 0x00ff00ff,  8 );
          merge( b2, b3, 0x00ff00ff,  8 );
          merge( b4, b5, 0x00ff00ff,  8 );
          merge( b6, b7, 0x00ff00ff,  8 );

          merge( b0, b4, 0x0f0f0f0f,  4 );
          merge( b1, b5, 0x0f0f0f0f,  4 );
          merge( b2, b6, 0x0f0f0f0f,  4 );
          merge( b3, b7, 0x0f0f0f0f,  4 );

          merge( b0, b2, 0x33333333,  2 );
          merge( b1, b3, 0x33333333,  2 );
          merge( b4, b6, 0x33333333,  2 );
          merge( b5, b7, 0x33333333,  2 );

          merge( b0, b1, 0x55555555,  1 );
          merge( b2, b3, 0x55555555,  1 );
          merge( b4, b5, 0x55555555,  1 );
          merge( b6, b7, 0x55555555,  1 );

          *plane[ 7 ]++ = b0;
          *plane[ 6 ]++ = b1;
          *plane[ 5 ]++ = b2;
          *plane[ 4 ]++ = b3;
          *plane[ 3 ]++ = b4;
          *plane[ 2 ]++ = b5;
          *plane[ 1 ]++ = b6;
          *plane[ 0 ]++ = b7;
        }
        else
        {
          plane[ 7 ]++;
          plane[ 6 ]++;
          plane[ 5 ]++;
          plane[ 4 ]++;
          plane[ 3 ]++;
          plane[ 2 ]++;
          plane[ 1 ]++;
          plane[ 0 ]++;
        }
      }
    }
    else
    {
      /* Process bitmaps */
      for( i = 0UL ; i < numcycles ; i++ )
      {
        register ULONG b0, b1, b2, b3, b4, b5, b6, b7,
                       tmp;

        /* process 32 pixels */
        b0 = *chunky++;  b4 = *chunky++;
        b1 = *chunky++;  b5 = *chunky++;
        b2 = *chunky++;  b6 = *chunky++;
        b3 = *chunky++;  b7 = *chunky++;

        merge( b0, b2, 0x0000ffff, 16 );
        merge( b1, b3, 0x0000ffff, 16 );
        merge( b4, b6, 0x0000ffff, 16 );
        merge( b5, b7, 0x0000ffff, 16 );

        merge( b0, b1, 0x00ff00ff,  8 );
        merge( b2, b3, 0x00ff00ff,  8 );
        merge( b4, b5, 0x00ff00ff,  8 );
        merge( b6, b7, 0x00ff00ff,  8 );

        merge( b0, b4, 0x0f0f0f0f,  4 );
        merge( b1, b5, 0x0f0f0f0f,  4 );
        merge( b2, b6, 0x0f0f0f0f,  4 );
        merge( b3, b7, 0x0f0f0f0f,  4 );

        merge( b0, b2, 0x33333333,  2 );
        merge( b1, b3, 0x33333333,  2 );
        merge( b4, b6, 0x33333333,  2 );
        merge( b5, b7, 0x33333333,  2 );

        merge( b0, b1, 0x55555555,  1 );
        merge( b2, b3, 0x55555555,  1 );
        merge( b4, b5, 0x55555555,  1 );
        merge( b6, b7, 0x55555555,  1 );

        *plane[ 7 ]++ = b0;
        *plane[ 6 ]++ = b1;
        *plane[ 5 ]++ = b2;
        *plane[ 4 ]++ = b3;
        *plane[ 3 ]++ = b4;
        *plane[ 2 ]++ = b5;
        *plane[ 1 ]++ = b6;
        *plane[ 0 ]++ = b7;
      }
    }
}


static
int getbase2( int x )
{
    int i = 0,
        j = 1;

    while( x > j )
    {
      j *= 2;
      i++;
    }

    return( i );
}




/* Read and test */
static
BOOL ReadOK( struct ClassBase *cb, struct GIFDecoder *gifdec, void *buffer, ULONG len )
{
    if( (gifdec -> which_fh) == WHICHFH_FILE )
    {
      return( (BOOL)(Read( (gifdec -> file), buffer, len ) == len) );
    }
    else
    {
      /* Check if the request fit in out buffer... */
      if( (((gifdec -> buffer) - (gifdec -> file_buffer)) + len) <= (gifdec -> buffersize) )
      {
        CopyMem( (gifdec -> buffer), buffer, len );
        gifdec -> buffer += len;

        return( TRUE );
      }
    }

    return( FALSE );
}






