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

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

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

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

/* Use WPA8, but do only on demand */
#define DELTAWPA8 1

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


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

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

/* Maximum number of colors in a GIF picture */
#define MAXCOLORMAPSIZE (256)

#define MAX_LWZ_BITS (12)

/* GIF flags and test macro */
#define SORTED         (0x20) /* colors sorted, important first */
#define INTERLACE      (0x40) /* interlaced picture             */
#define LOCALCOLORMAP  (0x80) /* frames have their own colormap */
#define BitSet( byte, bit ) (((byte) & (bit)) == (bit))

/* Read and test */
#define ReadOK( file, buffer, len ) (FRead( (file), (buffer), (LONG)(len), 1L ) == 1L)

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

/* macros to get rid of Intel 80x86 byte order */
#define LOHI2UINT16( a, b ) ((UWORD)(((b) << 8)|(a)))

#define SWAPW(a)  ((WORD)(((UWORD)(a)>>8)+((((UWORD)(a)&0xff)<<8))))
#define SWAPUW(a) ((UWORD)(((UWORD)(a)>>8)+((((UWORD)(a)&0xff)<<8))))
#define SWAPL(a)  ((LONG)(((ULONG)(a)>>24)+(((ULONG)(a)&0xff0000)>>8)+(((ULONG)(a)&0xff00)<<8)+(((ULONG)(a)&0xff)<<24)))
#define SWAPUL(a) ((ULONG)(((ULONG)(a)>>24)+(((ULONG)(a)&0xff0000)>>8)+(((ULONG)(a)&0xff00)<<8)+(((ULONG)(a)&0xff)<<24)))

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

/* GIF decoder context data */
struct GIFDecoder
{
    BPTR file;

    struct
    {
      UWORD                Width;
      UWORD                Height;
      struct ColorRegister ColorMap[ MAXCOLORMAPSIZE ]; /* colormap         */
      UWORD                BitPixel;                    /* number of colors */
      UWORD                ColorResolution;
      UBYTE                Background;                  /* background pen   */
      UWORD                AspectRatio;
    } GifScreen;

    struct
    {
      UWORD transparent; /* transparent pen; ~0U for nothing transparent */
      UWORD delayTime;   /* frame delay in 1/100 sec */
      BOOL  inputFlag;   /* user input before continue ? */
      UBYTE disposal;
#define GIF89A_DISPOSE_NOP                  (0U) /* No disposal specified. The decoder is not required to take any action. */
#define GIF89A_DISPOSE_NODISPOSE            (1U) /* Do not dispose. The graphic is to be left in place. */
#define GIF89A_DISPOSE_RESTOREBACKGROUND    (2U) /* Restore to background color. The area used by the graphic must be restored to the background color. */
#define GIF89A_DISPOSE_RESTOREPREVIOUS      (3U) /* Restore to previous. The decoder is required to restore the area overwritten by the graphic with what was there prior to rendering the graphic. */
#define GIF89A_DISPOSE_RESERVED4            (4U)
#define GIF89A_DISPOSE_RESERVED5            (5U)
#define GIF89A_DISPOSE_RESERVED6            (6U)
#define GIF89A_DISPOSE_RESERVED7            (7U)
    } Gif89; /* = { (UWORD)~0U, (UWORD)~0U, FALSE, GIF89A_DISPOSE_NOP };*/

    /* GetCode static vars */
    struct
    {
      UBYTE    buf[ 280 ];
      int      curbit,
               lastbit,
               last_byte;
      BOOL     done;
    } GetCode;

    BOOL     ZeroDataBlock; /* defaults to FALSE */

    /* LWZReadByte static vars */
    struct
    {
      BOOL     fresh /*= FALSE*/;
      int      code_size,
               set_code_size;
      int      max_code,
               max_code_size;
      int      firstcode,
               oldcode;
      int      clear_code,
               end_code;
      short    table[ 2 ][ (1 << MAX_LWZ_BITS) ];
      short    stack[ (1 << (MAX_LWZ_BITS)) * 2 ],
              *sp;
    } LWZReadByte;
};

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

/* 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, struct ColorRegister *, BOOL, BOOL );
static void             WritePixelArray8Fast( struct BitMap *, UBYTE *, UBYTE * );
static int              getbase2( int );
static void             IBMPC2ISOLatin1( STRPTR, STRPTR );

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

/* gifanim.datatype class instance data */
struct GIFAnimInstData
{
    /* Misc */
    struct SignalSemaphore  gaid_SigSem;          /* Instance data lock                      */
    UWORD                   gaid_Pad0;
    APTR                    gaid_Pool;
    struct BitMapHeader    *gaid_BMH;
    struct BitMap          *gaid_KeyBitMap;       /* Key BitMap                              */
    struct MinList          gaid_FrameList;       /* List of frames                          */
    STRPTR                  gaid_ProjectName;     /* Shortcut to DTA_Name                    */
    BPTR                    gaid_VerboseOutput;   /* Verbose output                          */

    /* Prefs */
    ULONG                   gaid_ModeID;
    BOOL                    gaid_LoadAll;         /* Load all frames of the animation        */
    BOOL                    gaid_Repeat;          /* Repeat mode ?                           */
    ULONG                   gaid_FPS;             /* fps of stream (maybe modified by prefs) */

    /* Sample stuff */
    BYTE                   *gaid_Sample;
    ULONG                   gaid_SampleLength;
    ULONG                   gaid_Period;
    ULONG                   gaid_Volume;
    ULONG                   gaid_SamplesPerFrame;

    /* Disk-loading section */
    BPTR                    gaid_FH;
    LONG                    gaid_CurrFilePos;

    /* decoder specific data */
    struct GIFDecoder       gaid_GIFDec;
};


/* node which holds information about a single animation frame */
struct FrameNode
{
    struct MinNode     fn_Node;

/* Misc */
    WORD               fn_UseCount;
    UWORD              fn_Pad0;

/* Timing section */
    ULONG              fn_TimeStamp;
    ULONG              fn_Frame;
    ULONG              fn_Duration;

/* Bitmap/ColorMap section */
    struct BitMap     *fn_BitMap;
    struct ColorMap   *fn_CMap;

    UBYTE             *fn_ChunkyMap; /* bitmap data in chunky version */

/* BitMap loading section */
    LONG               fn_BMOffset; /* File offset (0 is begin of file) */
    ULONG              fn_BMSize;   /* Chunk size  */

/* Sample section */
    BYTE              *fn_Sample;
    ULONG              fn_SampleLength;
    ULONG              fn_Period;
};


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


/* local prototypes */
static                 STRPTR               GetPrefsVar( struct ClassBase *, STRPTR );
static                 BOOL                 matchstr( struct ClassBase *, STRPTR, STRPTR );
static                 void                 ReadENVPrefs( struct ClassBase *, struct GIFAnimInstData * );
static                 LONG                 LoadFrames( struct ClassBase *, Object * );
static                 struct FrameNode    *AllocFrameNode( struct ClassBase *, APTR );
static                 struct FrameNode    *FindFrameNode( struct MinList *, ULONG );
static                 void                 FreeFrameNodeResources( struct ClassBase *, struct MinList * );
static                 void                 CopyBitMap( struct ClassBase *, struct BitMap *, struct BitMap * );
static                 struct BitMap       *AllocBitMapPooled( struct ClassBase *, ULONG, ULONG, ULONG, APTR );
static                 BOOL                 CMAP2Object( struct ClassBase *, Object *, UBYTE *, ULONG );
static                 struct ColorMap     *CMAP2ColorMap( struct ClassBase *, struct GIFAnimInstData *, UBYTE *, ULONG );
static                 struct ColorMap     *CopyColorMap( struct ClassBase *, struct ColorMap * );
static                 APTR                 AllocVecPooled( struct ClassBase *, APTR, ULONG );
static                 void                 FreeVecPooled( struct ClassBase *, APTR, APTR );
static                 struct FrameNode    *GetPrevFrameNode( struct FrameNode *, ULONG );
static                 void                 OpenLogfile( struct ClassBase *, struct GIFAnimInstData * );
static                 void                 mysprintf( struct ClassBase *, STRPTR, STRPTR, ... );
static                 void                 verbose_printf( struct ClassBase *, struct GIFAnimInstData *, STRPTR, ... );
static                 void                 error_printf( struct ClassBase *, struct GIFAnimInstData *, STRPTR, ... );
static                 void                 AttachSample( struct ClassBase *, struct GIFAnimInstData * );

static                 ULONG                SaveGIFAnim( struct ClassBase *, struct IClass *, Object *, struct dtWrite * );


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

/* 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 ) )
    {
      cl -> cl_Dispatcher . h_Entry = (HOOKFUNC)Dispatch;
      cl -> cl_UserData             = (ULONG)cb;

      AddClass( cl );
    }

    return( cl );
}

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

struct MyStackSwapStruct
{
    struct StackSwapStruct  stk;
    struct IClass          *cl;
    Object                 *o;
    Msg                     msg;
};

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


DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase         *cb = (struct ClassBase *)(cl -> cl_UserData);
    ULONG                     retval;
    struct MyStackSwapStruct  mystk;
    UBYTE                    *lower,
                             *upper,
                             *sp;
    struct Task              *ThisTask;
    ULONG                     stacksize;

    mystk . cl                = cl;
    mystk . o                 = o;
    mystk . msg               = msg;

    ThisTask = FindTask( NULL );
    stacksize = (ULONG)(((UBYTE *)(ThisTask -> tc_SPReg)) - ((UBYTE *)(ThisTask -> tc_SPLower)));

#define DTSTACKSIZE (16384UL)

    /* Enougth stack ? */
    if( stacksize > (DTSTACKSIZE / 2UL) )
    {
      retval = MyDispatch( (&mystk) );
    }
    else
    {
      /* Alloc a new stack frame... */
      while( !(lower = (UBYTE *)AllocMem( DTSTACKSIZE, MEMF_PUBLIC )) );

      sp = upper = lower + DTSTACKSIZE;

      mystk . stk . stk_Lower   = lower;
      mystk . stk . stk_Upper   = (ULONG)upper;
      mystk . stk . stk_Pointer = sp;

      retval = SwapMe( (&mystk) );

      FreeMem( lower, DTSTACKSIZE );
    }

    return( retval );
}


DISPATCHERFLAGS
ULONG SwapMe( REGA0 struct MyStackSwapStruct *mystk )
{
    register ULONG retval;

#define cb ((struct ClassBase *)(mystk -> cl -> cl_UserData))

    StackSwap( (&(mystk -> stk)) );

      retval = MyDispatch( mystk );

    StackSwap( (&(mystk -> stk)) );

#undef cb

    return( retval );
}


/* class dispatcher */
DISPATCHERFLAGS
ULONG MyDispatch( REGA0 struct MyStackSwapStruct *mystk )
{
    struct IClass           *cl  = mystk -> cl;
    Object                  *o   = mystk -> o;
    Msg                      msg = mystk -> 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.
*
*        Subclasses of gifanim.datatype are not supported. Any attempt to
*        create a subclass object of gifanim.datatype will be rejected by this
*        method.
*
*    ATTRIBUTES
*        The following attributes can be specified at creation time.
*
*        DTA_SourceType (ULONG) -- Determinates the type of DTA_Handle
*            attribute. Only DTST_FILE is 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)
#ifdef HAS_ENCODER
&& ((ti -> ti_Data) != DTST_RAM)
#endif /* HAS_ENCODER */
 )
            {
              SetIoErr( ERROR_OBJECT_WRONG_TYPE );

              break;
            }
          }

          if( retval = DoSuperMethodA( cl, o, msg ) )
          {
            LONG error;

            /* Load frames... */
            if( error = LoadFrames( cb, (Object *)retval ) )
            {
              /* Something went fatally wrong, dispose object */
              CoerceMethod( cl, (Object *)retval, OM_DISPOSE );
              retval = 0UL;
            }

            SetIoErr( error );
          }
      }
          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:
      {
          /* 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. */
          FreeFrameNodeResources( cb, (&(gaid -> gaid_FrameList)) );

          /* 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 );
      }
          break;

      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 );

                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 Anim 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 not supported yet... */
          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 FrameNode *fn;
          struct adtFrame  *alf;
          LONG              error = 0L;

          gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );
          alf = (struct adtFrame *)msg;
          
          ObtainSemaphore( (&(gaid -> gaid_SigSem)) );

          /* Like "realloc": Free any given frame here */
          if( alf -> alf_UserData )
          {
            msg -> MethodID = ADTM_UNLOADFRAME;

              DoMethodA( o, msg );

            msg -> MethodID = ADTM_LOADFRAME;
          }

          /* 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)...
             */
            if( ((gaid -> gaid_LoadAll) == FALSE) && (gaid -> gaid_FH) )
            {
              /* If no bitmap is loaded, load it... */
              if( (fn -> fn_BitMap) == NULL )
              {
                ULONG animwidth  = (ULONG)(gaid -> gaid_BMH -> bmh_Width),
                      animheight = (ULONG)(gaid -> gaid_BMH -> bmh_Height),
                      animdepth  = (ULONG)(gaid -> gaid_BMH -> bmh_Depth);

                /* Allocate array for chunkypixel data */
                if( fn -> fn_ChunkyMap = (UBYTE *)AllocVecPooled( cb, (gaid -> gaid_Pool), ((animwidth * animheight) + 256) ) )
                {
                  if( fn -> fn_BitMap = AllocBitMapPooled( cb, animwidth, animheight, animdepth, (gaid -> gaid_Pool) ) )
                  {
                    struct FrameNode *worknode = fn;
                    struct FrameNode *prevnode = NULL;
                    ULONG             rollback = 0UL;
                    UBYTE            *deltamap = NULL;

#ifdef DEBUG_ALF
                    verbose_printf( cb, gaid, "frame %lu ", (fn -> fn_TimeStamp) );
#endif /* DEBUG_ALF */

                    /* 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( gaid -> gaid_GIFDec . Gif89 . disposal )
                    {
                      case GIF89A_DISPOSE_NODISPOSE:
                      case GIF89A_DISPOSE_RESTOREPREVIOUS:
                      {
                          do
                          {
                            worknode = GetPrevFrameNode( worknode, 1UL );

                            rollback++;

#ifdef DEBUG_ALF
                            verbose_printf( cb, gaid, "r %lu ", (worknode -> fn_TimeStamp) );
#endif /* DEBUG_ALF */
                          } while( ((worknode -> fn_ChunkyMap) == NULL) && ((worknode -> fn_TimeStamp) != 0UL) );
                      }
                          break;
                    }

#ifdef DEBUG_ALF
                    verbose_printf( cb, gaid, "rollback %lu ", rollback );
#endif /* DEBUG_ALF */

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

#ifdef DEBUG_ALF
                    verbose_printf( cb, gaid, "l " );
#endif /* DEBUG_ALF */

                    do
                    {
                      ULONG current = rollback;

                      worknode = fn;

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

#ifdef DEBUG_ALF
                      verbose_printf( cb, gaid, "%lu:%lu ", rollback, (worknode -> fn_TimeStamp) );
#endif /* DEBUG_ALF */

                      if( (worknode -> fn_ChunkyMap) && (worknode != fn) )
                      {
#ifdef DEBUG_ALF
                        verbose_printf( cb, gaid, "CP " );
#endif /* DEBUG_ALF */
                        prevnode = worknode;
                      }
                      else
                      {
#ifdef DEBUG_ALF
                        verbose_printf( cb, gaid, "LO " );
#endif /* DEBUG_ALF */

                        if( Seek( (gaid -> gaid_FH), ((worknode -> fn_BMOffset) - (gaid -> gaid_CurrFilePos)), OFFSET_CURRENT ) != (-1L) )
                        {
                          BOOL   useGlobalColormap;
                          UWORD  bitPixel;
                          UBYTE  buf[ 16 ];

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

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

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

                          /* disposal method */
                          switch( gaid -> gaid_GIFDec . Gif89 . disposal )
                          {
                            case GIF89A_DISPOSE_NOP:
                            {
                                /* 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) );
                                  deltamap = prevnode -> fn_ChunkyMap;
#endif /* DELTAWPA8 */
                                }
                                else
                                {
#ifdef DELTAWPA8
                                  deltamap = NULL;
#endif /* DELTAWPA8 */
                                }
                            }
                                break;

                            case GIF89A_DISPOSE_RESTOREBACKGROUND:
                            {
                                /* 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) );
                                  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 )
                          {
                            struct ColorRegister localColorMap[ MAXCOLORMAPSIZE ];

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

                            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 ] ),
                                       localColorMap,
                                       BitSet( buf[ 8 ], INTERLACE ),
                                       FALSE );
                          }
                          else
                          {
                            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 ] ),
                                       (gaid -> gaid_GIFDec . GifScreen . ColorMap),
                                       BitSet( buf[ 8 ], INTERLACE ),
                                       FALSE );
                          }

                          /* 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-- );

#ifdef DEBUG_ALF
                    verbose_printf( cb, gaid, ".\n" );
#endif /* DEBUG_ALF */

                    if( error == 0L )
                    {
                      if( fn -> fn_ChunkyMap )
                      {
                        WritePixelArray8Fast( (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   = fn -> fn_CMap;

            /* 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  */
            SetIoErr( error );                                   /* Result2 */
          }
          else
          {
            /* no matching frame found */
            SetIoErr( ERROR_OBJECT_NOT_FOUND );
          }

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

/****** gifanim.datatype/ADTM_UNLOADFRAME ************************************
*
*    NAME
*        ADTM_UNLOADFRAME -- Load 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)) )
                {
                  FreeVecPooled( cb, (gaid -> gaid_Pool), (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 );
}


/****** gifanim.datatype/preferences *****************************************
*
*   NAME
*       preferences
*
*   DESCRIPTION
*       The "ENV:Classes/DataTypes/gifanim.prefs" file contains global
*       settings for the datatype.
*       The preferences file is an ASCII file containing one line where the
*       preferences can be set.
*       It can be superset by a local variable with the same name.
*
*       Each line can contain settings, special settings for some projects
*       can be set using the MATCHPROJECT option.
*       Lines beginning with a '#' or ';' chars are treated as comments.
*       Lines are limitted to 256 chars.
*
*   TEMPLATE
*       MATCHPROJECT/K,VERBOSE/S,MODEID/K/N,FPS/K/N,REPEAT/S,NOREPEAT/S,
*       SAMPLE/K,SAMPLESPERFRAME=SPF/K/N,VOLUME/K/N,LOADALL/S,NOLOADALL/S
*
*       MATCHPROJECT -- The settings in this line belongs only to this
*           project(s), e.g. if the case-insensitive pattern does not match,
*           this line is ignored.
*           The maximum length of the pattern is 128 chars.
*           Defaults to #?, which matches any project.
*
*       VERBOSE -- Print information about the animation. Currently
*          the frame numbers and the used compression are printed, after all
*          number of scanned/loaded frames, set FPS rate, dimensions (width/
*          height/depth), sample information etc.
*
*       MODEID -- Select screen mode id of datatype (will be stored in
*           ADTA_ModeID). Note that the DOS ReadArgs function used for parsing
*           fetches a SIGNED long. The bit 31 will be represented by minus
*           '-'. (example: "MODEID=266240" sets the mode to the A2024 screen
*           mode id)
*           Defaults to 0, which means: Use the best screenmode available for 
*           the given width, height and depth.
*
*       FPS -- Frames Per Second
*           A value of 0 here means: Use default FPS.
*
*       REPEAT -- Turn on repeat mode, e.g. DTA_Repeat == TRUE
*
*       NOREPEAT -- Turns REPEAT mode off.
*
*       SAMPLE -- Attach the given sample to the animation. The sample will
*           be loaded using datatypes (GID_SOUND).
*           Only one sample can be attached to one animation stream, any
*           following attempt to attach a sample will be ignored.
*
*       SAMPLESPERFRAME -- Set samples per frame rate for sound. This
*           overrides the own internal calculations to get rid of rounding
*           errors.
*
*       VOLUME -- Volume of the sound when playing.
*           Defaults to 64, which is the maximum. A value greater than 64 will
*           be set to 64.
*
*       LOADALL -- Load all frames into memory.
*
*       NOLOADALL -- Turns off the LOADALL flag, which may be set in a prefs-
*           line before. This switch is set per default, and can be turned off
*           by the LOADALL option, later it can be turned on again by this
*           option.
*
*
*   NOTE
*       - An invalid prefs file line will be ignored and forces the VERBOSE
*         output.
*
*       - REPEAT mode may be a hack.
*
*   BUGS
*       - Low memory may cause that the prefs file won't be parsed.
*
*       - Lines are limitted to 256 chars
*
*       - An invalid prefs file line will be ignored.
*
*       - The sample path length is limitted to 200 chars. A larger
*         value may crash the machine if an error occurs.
*
******************************************************************************
*
*/


static
STRPTR GetPrefsVar( struct ClassBase *cb, STRPTR name )
{
          STRPTR buff;
    const ULONG  buffsize = 16UL;

    if( buff = (STRPTR)AllocVec( (buffsize + 2UL), (MEMF_PUBLIC | MEMF_CLEAR) ) )
    {
      if( GetVar( name, buff, buffsize, GVF_BINARY_VAR ) != (-1L) )
      {
        ULONG varsize = IoErr();

        varsize += 2UL;

        if( varsize > buffsize )
        {
          FreeVec( buff );

          if( buff = (STRPTR)AllocVec( (varsize + 2UL), (MEMF_PUBLIC | MEMF_CLEAR) ) )
          {
            if( GetVar( name, buff, varsize, GVF_BINARY_VAR ) != (-1L) )
            {
              return( buff );
            }
          }
        }
        else
        {
          return( buff );
        }
      }

      FreeVec( buff );
    }

    return( NULL );
}


static
BOOL matchstr( struct ClassBase *cb, STRPTR pat, STRPTR s )
{
    TEXT buff[ 512 ];

    if( pat && s )
    {
      if( ParsePatternNoCase( pat, buff, (sizeof( buff ) - 1) ) != (-1L) )
      {
        if( MatchPatternNoCase( buff, s ) )
        {
          return( TRUE );
        }
      }
    }

    return( FALSE );
}


static
void ReadENVPrefs( struct ClassBase *cb, struct GIFAnimInstData *gaid )
{
    struct RDArgs envvarrda =
    {
      NULL,
      256L,
      0L,
      0L,
      NULL,
      0L,
      NULL,
      RDAF_NOPROMPT
    };

    struct
    {
      STRPTR  matchproject;
      long   *verbose;
      long   *modeid;
      long   *fps;
      long   *repeat;
      long   *norepeat;
      STRPTR  sample;
      long   *samplesperframe;
      long   *volume;
      long   *loadall;
      long   *noloadall;
    } gifanimargs;

    TEXT   varbuff[ 258 ];
    STRPTR var;

    if( var = GetPrefsVar( cb, "Classes/DataTypes/gifanim.prefs" ) )
    {
      STRPTR prefsline      = var,
             nextprefsline;
      ULONG  linecount      = 1UL;

      /* Be sure that "var" contains at least one break-char */
      strcat( var, "\n" );

      while( nextprefsline = strpbrk( prefsline, "\n" ) )
      {
        stccpy( varbuff, prefsline, (int)MIN( (sizeof( varbuff ) - 2UL), (((ULONG)(nextprefsline - prefsline)) + 1UL) ) );

        /* be sure that this line isn't a comment line or an empty line */
        if( (varbuff[ 0 ] != '#') && (varbuff[ 0 ] != ';') && (varbuff[ 0 ] != '\n') && (strlen( varbuff ) > 2UL) )
        {
          /* Prepare ReadArgs processing */
          strcat( varbuff, "\n" );                                       /* Add NEWLINE-char            */
          envvarrda . RDA_Source . CS_Buffer = varbuff;                  /* Buffer                      */
          envvarrda . RDA_Source . CS_Length = strlen( varbuff ) + 1UL;  /* Set up input buffer length  */
          envvarrda . RDA_Source . CS_CurChr = 0L;
          envvarrda . RDA_Buffer = NULL;
          envvarrda . RDA_BufSiz = 0L;
          memset( (void *)(&gifanimargs), 0, sizeof( gifanimargs ) );          /* Clear result array          */

          if( ReadArgs( "MATCHPROJECT/K,"
                        "VERBOSE/S,"
                        "MODEID/K/N,"
                        "FPS/K/N,"
                        "REPEAT/S,"
                        "NOREPEAT/S,"
                        "SAMPLE/K,"
                        "SAMPLESPERFRAME=SPF/K/N,"
                        "VOLUME/K/N,"
                        "LOADALL/S,"
                        "NOLOADALL/S", (LONG *)(&gifanimargs), (&envvarrda) ) )
          {
            BOOL noignore = TRUE;

            if( (gifanimargs . matchproject) && (gaid -> gaid_ProjectName) )
            {
              noignore = matchstr( cb, (gifanimargs . matchproject), (gaid -> gaid_ProjectName) );
            }

            if( noignore )
            {
              if( gifanimargs . verbose )
              {
                OpenLogfile( cb, gaid );
              }

              if( gifanimargs . modeid )
              {
                gaid -> gaid_ModeID = *(gifanimargs . modeid);
              }

              if( gifanimargs . fps )
              {
                gaid -> gaid_FPS = *(gifanimargs . fps);
              }

              if( gifanimargs . repeat )
              {
                gaid -> gaid_Repeat = TRUE;
              }

              if( gifanimargs . norepeat )
              {
                gaid -> gaid_Repeat = FALSE;
              }

              if( gifanimargs . loadall )
              {
                gaid -> gaid_LoadAll = TRUE;
              }

              if( gifanimargs . noloadall )
              {
                gaid -> gaid_LoadAll = FALSE;
              }

              if( (gifanimargs . sample) && ((gaid -> gaid_Sample) == NULL) )
              {
                Object *so;
                LONG    ioerr = 0L;

                verbose_printf( cb, gaid, "loading sample \"%s\"...\n", (gifanimargs . sample) );

                if( so = NewDTObject( (gifanimargs . sample), DTA_GroupID, GID_SOUND, TAG_DONE ) )
                {
                  BYTE  *sample;
                  ULONG  length;
                  ULONG  period;

                  /* Get sample data from object */
                  if( GetDTAttrs( so, SDTA_Sample,       (&sample),
                                      SDTA_SampleLength, (&length),
                                      SDTA_Period,       (&period),
                                      TAG_DONE ) == 3UL )
                  {
                    if( gaid -> gaid_Sample = (STRPTR)AllocPooled( (gaid -> gaid_Pool), (length + 1UL) ) )
                    {
                      /* Copy sample and context */
                      CopyMem( (APTR)sample, (APTR)(gaid -> gaid_Sample), length );
                      gaid -> gaid_SampleLength = length;
                      gaid -> gaid_Period       = period;
                    }
                    else
                    {
                      /* Can't alloc sample */
                      ioerr = ERROR_NO_FREE_STORE;
                    }
                  }
                  else
                  {
                    /* Object does not support the requested attributes */
                    ioerr = ERROR_OBJECT_WRONG_TYPE;
                  }

                  DisposeDTObject( so );
                }
                else
                {
                  /* NewDTObjectA failed, cannot load sample... */
                  ioerr = IoErr();
                }

                if( (gaid -> gaid_Sample) == NULL )
                {
                  TEXT errbuff[ 256 ];

                  if( ioerr >= DTERROR_UNKNOWN_DATATYPE )
                  {
                    mysprintf( cb, errbuff, GetDTString( ioerr ), (gifanimargs . sample) );
                  }
                  else
                  {
                    Fault( ioerr, (gifanimargs . sample), errbuff, sizeof( errbuff ) );
                  }

                  error_printf( cb, gaid, "can't load sample: \"%s\" line %lu\n", errbuff, linecount );
                }
              }

              if( gifanimargs . samplesperframe )
              {
                gaid -> gaid_SamplesPerFrame = *(gifanimargs . samplesperframe);
              }

              if( gifanimargs . volume )
              {
                gaid -> gaid_Volume = *(gifanimargs . volume);

                if( (gaid -> gaid_Volume) > 64UL )
                {
                  gaid -> gaid_Volume = 64UL;
                }
              }
            }
            else
            {
              verbose_printf( cb, gaid, "prefs line %lu ignored\n", linecount );
            }

            FreeArgs( (&envvarrda) );
          }
          else
          {
            LONG ioerr = IoErr();
            TEXT errbuff[ 256 ];

            Fault( ioerr, "Classes/DataTypes/gifanim.prefs", errbuff, (LONG)sizeof( errbuff ) );

            error_printf( cb, gaid, "preferences \"%s\" line %lu\n", errbuff, linecount );
          }
        }

        prefsline = ++nextprefsline;
        linecount++;
      }

      FreeVec( var );
    }
}


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

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

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

      /* Prefs defaults */
      gaid -> gaid_LoadAll = TRUE; /* only for testing */
      gaid -> gaid_Volume  = 64UL;

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

      /* Get file handle, handle type and BitMapHeader */
      if( GetDTAttrs( o, DTA_SourceType,    (&sourcetype),
                         DTA_Handle,        (&fh),
                         DTA_Name,          (&(gaid -> gaid_ProjectName)),
                         PDTA_BitMapHeader, (&bmh),
                         TAG_DONE ) == 4UL )
      {
        gaid -> gaid_BMH = bmh; /* Store BitMapHeader */

        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 )) )
                  {
                    /* 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 )) )
                {
                  /* Can't open file */
                  error = IoErr();
                }
              }
          }
              break;

          case DTST_RAM:
          {
              /* do nothing */
          }
              break;

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

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

            /* Bump fh buffer size to 4k */
            (void)SetVBuf( fh, NULL, BUF_FULL, 4096L );

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

            if( ReadOK( (gifdec -> file), buf, 6 ) )
            {
              if( strncmp( (char *)buf, "GIF", 3 ) == 0 )
              {
                strncpy( version, (char *)(buf + 3), 3 );
                version[ 3 ] = '\0';

                if( (strcmp( version, "87a" ) == 0) ||
                    (strcmp( version, "89a" ) == 0) )
                {
                  if( ReadOK( (gifdec -> file), 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 ];

                    animwidth  = bmh -> bmh_Width  = (gifdec -> GifScreen . Width + 15UL) & ~15UL; /* align for wpa */
                    animheight = bmh -> bmh_Height = gifdec -> GifScreen . Height;
                    animdepth  = bmh -> bmh_Depth  = (ULONG)getbase2( (gifdec -> GifScreen . BitPixel) );

                    /* Global Colormap ? */
                    if( BitSet( buf[ 4 ], LOCALCOLORMAP ) )
                    {
                      if( ReadColorMap( cb, gaid, (gifdec -> GifScreen . BitPixel), (gifdec -> GifScreen . ColorMap) ) )
                      {
                        D( kprintf( "error reading global colormap\n" ) );
                        error = IoErr();
                      }
                    }

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

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

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

                            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 = AllocBitMapPooled( cb, (ULONG)animwidth, (ULONG)animheight, (ULONG)animdepth, (gaid -> gaid_Pool)) ) )
                                {
                                  error = ERROR_NO_FREE_STORE;
                                }

                                /* Allocate array for chunkypixel data */
                                if( !(fn -> fn_ChunkyMap = (UBYTE *)AllocVecPooled( cb, (gaid -> gaid_Pool), ((animwidth * animheight) + 256) )) )
                                {
                                  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( (gifdec -> file), buf, 9 ) )
                                {
                                  D( kprintf( "couldn't read left/top/width/height\n" ) );
                                  error = IoErr();
                                }

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

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

                                if( fn -> fn_ChunkyMap )
                                {
                                  /* disposal method */
                                  switch( gifdec -> Gif89 . disposal )
                                  {
                                    case GIF89A_DISPOSE_NOP:
                                    {
                                        /* 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) );
                                          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;

                                    case GIF89A_DISPOSE_RESTOREBACKGROUND:
                                    {
                                        /* 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) );
                                          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;
                                  }
                                }

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

                                  if( timestamp == 0UL )
                                  {
                                    if( !CMAP2Object( cb, o, (UBYTE *)localColorMap, (ULONG)(bitPixel * 3UL) ) )
                                    {
                                      /* can't alloc object's color table */
                                      error = ERROR_NO_FREE_STORE;
                                    }
                                  }

                                  if( !(fn -> fn_CMap = CMAP2ColorMap( cb, gaid, (UBYTE *)localColorMap, (ULONG)(bitPixel * 3UL) )) )
                                  {
                                    /* can't alloc object's color table */
                                    error = ERROR_NO_FREE_STORE;
                                  }

                                  numcmaps++;

                                  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 ] ),
                                             localColorMap,
                                             BitSet( buf[ 8 ], INTERLACE ),
                                             ((fn -> fn_BitMap) == NULL) );
                                }
                                else
                                {
                                  /* First frame gets global colormap */
                                  if( timestamp == 0UL )
                                  {
                                    fn -> fn_CMap = CMAP2ColorMap( cb, gaid, (UBYTE *)(gifdec -> GifScreen . ColorMap), (ULONG)((gifdec -> GifScreen . BitPixel) * 3UL) );

                                    if( !CMAP2Object( cb, o, (UBYTE *)(gifdec -> GifScreen . ColorMap), (ULONG)((gifdec -> GifScreen . BitPixel) * 3UL) ) )
                                    {
                                      /* can't alloc object's color table */
                                      error = ERROR_NO_FREE_STORE;
                                    }

                                    numcmaps++;
                                  }

                                  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 ] ),
                                             (gifdec -> GifScreen . ColorMap),
                                             BitSet( buf[ 8 ], INTERLACE ),
                                             ((fn -> fn_BitMap) == NULL) );
                                }

                                /* 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 )
                                {
                                  WritePixelArray8Fast( (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;

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

                    error_printf( cb, gaid, "unusual leave\n" );

scandone:
                    /* Any frames ? */
                    if( timestamp && (error == 0L) && numcmaps )
                    {
                      if( numcmaps == 1UL )
                      {
                        /* We only have a global colormap and no colormap changes,
                         * 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;

                        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 )
                    {
                      /* test test: get max timestamp. Don't ask for the calculation scheme, it's crap */
                      timestamp = (((struct FrameNode *)(gaid -> gaid_FrameList . mlh_TailPred)) -> fn_TimeStamp) -
                                  (((struct FrameNode *)(gaid -> gaid_FrameList . mlh_TailPred)) -> fn_Duration);

                      /* Alloc bitmap as key bitmap  */
                      if( gaid -> gaid_KeyBitMap = AllocBitMap( animwidth, animheight, animdepth, BMF_CLEAR, NULL ) )
                      {
                        struct FrameNode *firstfn = (struct FrameNode *)(gaid -> gaid_FrameList . mlh_Head); /* short cut to the first FrameNode */

                        if( (firstfn -> fn_BitMap) == NULL )
                        {
                          if( !(firstfn -> fn_BitMap = AllocBitMapPooled( cb, (ULONG)(gaid -> gaid_BMH -> bmh_Width), (ULONG)(gaid -> gaid_BMH -> bmh_Height), (ULONG)(gaid -> gaid_BMH -> bmh_Depth), (gaid -> gaid_Pool) )) )
                          {
                            /* 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) );

                          if( gaid -> gaid_ModeID )
                          {
                            modeid = gaid -> gaid_ModeID;
                          }
                          else
                          {
                            /* No mode id ? */
                            if( modeid == 0UL )
                            {
                              /* Get best modeid for this dimensions */
                              modeid = BestModeID( BIDTAG_NominalWidth,      animwidth,
                                                   BIDTAG_NominalHeight,     animheight,
                                                   BIDTAG_Depth,             animdepth,
                                                   BIDTAG_DIPFMustNotHave,   (DIPF_IS_DUALPF | DIPF_IS_PF2PRI),
                                                   TAG_DONE );
                            }
                          }

                          if( (gaid -> gaid_FPS) == 0UL )
                          {
                            gaid -> gaid_FPS = 100; /* defaults to 100 fps */
                          }

                          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,
                                      DTA_Repeat,           (gaid -> gaid_Repeat),
                                      ADTA_Width,           animwidth,
                                      ADTA_Height,          animheight,
                                      ADTA_Depth,           animdepth,
                                      ADTA_Frames,          timestamp,
                                      ADTA_FramesPerSecond, (gaid -> gaid_FPS),
                                      ADTA_ModeID,          modeid,
                                      ADTA_KeyFrame,        (gaid -> gaid_KeyBitMap),
                                      ADTA_Sample,          (firstfn -> fn_Sample),
                                      ADTA_SampleLength,    (firstfn -> fn_SampleLength),
                                      ADTA_Period,          (firstfn -> fn_Period),
                                      ADTA_Volume,          (gaid -> gaid_Volume),
                                      ADTA_Cycles,          1UL,
                                      TAG_DONE );
                        }
                      }
                      else
                      {
                        /* can't alloc key bitmap */
                        error = ERROR_NO_FREE_STORE;
                      }
                    }
                  }
                  else
                  {
                    D( kprintf( "failed to read screen descriptor\n" ) );
                    error = IoErr();
                  }
                }
                else
                {
                  D( kprintf( "bad version number, not '87a' or '89a'\n" ) );
                  error = DTERROR_UNKNOWN_COMPRESSION;
                }
              }
              else
              {
                D( kprintf( "not a GIF file\n" ) );
                error = ERROR_OBJECT_WRONG_TYPE;
              }
            }
            else
            {
              D( kprintf( "error reading magic number\n" ) );
              error = IoErr();
            }

            /* 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 )
            {
              /* 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;
    }

    return( error );
}


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 MinList *fnl )
{
    if( fnl )
    {
      struct FrameNode *worknode,
                       *nextnode;

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

      while( nextnode = (struct FrameNode *)(worknode -> fn_Node . mln_Succ) )
      {
        if( worknode -> fn_CMap )
        {
          FreeColorMap( (worknode -> fn_CMap) );
          worknode -> fn_CMap = NULL;
        }

        worknode = nextnode;
      }
    }
}


static
void CopyBitMap( struct ClassBase *cb, struct BitMap *dest, struct BitMap *src )
{
    if( dest && src )
    {
      ULONG planesize = (ULONG)(dest -> BytesPerRow) * (ULONG)(dest -> Rows);
      UWORD i;

      for( i = 0U ; i < (dest -> Depth) ; i++ )
      {
        CopyMem( (src -> Planes[ i ]), (dest -> Planes[ i ]), planesize );
      }
    }
}


/* 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 );
}


static
BOOL CMAP2Object( struct ClassBase *cb, Object *o, UBYTE *rgb, ULONG rgbsize )
{
    struct ColorRegister *acm;
    ULONG                *acregs;
    ULONG                 nc;

    /* file has this many colors (e.g. each color has one byte per R,B,G-gun) */
    nc = rgbsize / 3UL;

    SetDTAttrs( o, NULL, NULL, ADTA_NumColors, nc, TAG_DONE );

    /* Get color context */
    if( GetDTAttrs( o,
                    ADTA_ColorRegisters, (&acm),
                    ADTA_CRegs,          (&acregs),
                    ADTA_NumColors,      (&nc),
                    TAG_DONE ) == 3UL )
    {
      /* All valid ? */
      if( acm && acregs && nc )
      {
        ULONG i;

        for( i = 0UL ; i < nc ; i++, acm++ )
        {
          acm -> red   =  *rgb++;
          acm -> green =  *rgb++;
          acm -> blue  =  *rgb++;

          /* Replicate the color information.
           * This surrounds an OS bug which uses the low-order bytes of the 32-bit colors
           * instead of the high order ones
           */
          acregs[ ((i * 3) + 0) ] = ((ULONG)(acm -> red))   * 0x01010101UL;
          acregs[ ((i * 3) + 1) ] = ((ULONG)(acm -> green)) * 0x01010101UL;
          acregs[ ((i * 3) + 2) ] = ((ULONG)(acm -> blue))  * 0x01010101UL;
        }

        return( TRUE );
      }
    }

    return( FALSE );
}


static
struct ColorMap *CMAP2ColorMap( struct ClassBase *cb, struct GIFAnimInstData *gaid, UBYTE *rgb, ULONG rgbsize )
{
    struct ColorMap *cm;
    ULONG            a_nc   = (1UL << (ULONG)(gaid -> gaid_BMH -> bmh_Depth)); /* Number of colors in animation */
    ULONG            rgb_nc = rgbsize / 3UL;                                   /* Number of colors in CMAP      */

    /* Get a colormap which hold all colors */
    if( cm = GetColorMap( (long)MAX( a_nc, rgb_nc ) ) )
    {
      ULONG i,
            r, g, b;

      for( i = 0UL ; i < rgb_nc ; i++ )
      {
        r = *rgb++;
        g = *rgb++;
        b = *rgb++;

        /* Replicate color information (see CMAP2Object for details) and store them into colormap */
        SetRGB32CM( cm, i, (r * 0x01010101UL), (g * 0x01010101UL), (b * 0x01010101UL) );
      }

      /* BUG: the remaining entries should be filled with colors from the last colormap */
      for( ; i < a_nc ; i++ )
      {
        SetRGB32CM( cm, i, 0UL, 0UL, 0UL ); /* fill remaining entries with black */
      }
    }

    return( cm );
}


static
struct ColorMap *CopyColorMap( struct ClassBase *cb, struct ColorMap *src )
{
    struct ColorMap *dest = NULL;

    if( src )
    {
      ULONG *ctable;

      if( ctable = (ULONG *)AllocVec( ((ULONG)(src -> Count) * sizeof( ULONG ) * 3UL), MEMF_PUBLIC ) )
      {
        if( dest = GetColorMap( (long)(src -> Count) ) )
        {
          ULONG i;

          GetRGB32( src, 0UL, (ULONG)(src -> Count), ctable );

          for( i = 0UL ; i < (src -> Count) ; i++ )
          {
            SetRGB32CM( dest, i, ctable[ ((i * 3) + 0) ], ctable[ ((i * 3) + 1) ], ctable[ ((i * 3) + 2) ] );
          }
        }

        FreeVec( ctable );
      }
    }

    return( dest );
}



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


static
APTR AllocVecPooled( struct ClassBase *cb, APTR pool, ULONG memsize )
{
    ULONG *memory = NULL;

    if( pool && memsize )
    {
      memsize += (ULONG)sizeof( ULONG );

      if( memory = (ULONG *)AllocPooled( pool, memsize ) )
      {
        (*memory) = memsize;

        memory++;
      }
    }

    return( (APTR)memory );
}


static
void FreeVecPooled( struct ClassBase *cb, APTR pool, APTR mem )
{
    if( pool && mem )
    {
      ULONG *memory;

      memory = (ULONG *)mem;

      memory--;

      FreePooled( pool, memory, (*memory) );
    }
}


static
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 );
      }
    }
}


static
void mysprintf( struct ClassBase *cb, STRPTR buffer, STRPTR fmt, ... )
{
    APTR args;

    args = (APTR)((&fmt) + 1);

    RawDoFmt( fmt, args, (void (*))"\x16\xc0\x4e\x75", buffer );
}


static
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) );
    }
}


static
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
ULONG SaveGIFAnim( struct ClassBase *cb, struct IClass *cl, Object *o, struct dtWrite *dtw )
{
    ULONG retval = 0UL;
    LONG  error  = 0L;

#ifdef HAS_ENCODER
    /* A NULL file handle is a nop (GMultiView uses this to test if a datatype supports RAW writing) */
    if( dtw -> dtw_FileHandle )
    {
      struct GIFAnimInstData *gaid = (struct GIFAnimInstData *)INST_DATA( cl, o );

      struct BitMapHeader *bmh;
      ULONG                modeid;
      ULONG               *cregs;
      ULONG                numcolors;
      ULONG                startframe = 0UL,
                           numframes  = 0UL,
                           framestep  = 1UL;
      ULONG                fps = 0UL;
      struct BitMap       *keyframe;
      ULONG                animwidth,
                           animheight,
                           animdepth;

      if( GetDTAttrs( o, PDTA_BitMapHeader,     (&bmh),
                         ADTA_ModeID,           (&modeid),
                         ADTA_CRegs,            (&cregs),
                         ADTA_NumColors,        (&numcolors),
                         ADTA_Width,            (&animwidth),
                         ADTA_Height,           (&animheight),
                         ADTA_Depth,            (&animdepth),
                         ADTA_Frame,            (&startframe),
                         ADTA_Frames,           (&numframes),
                         ADTA_FramesPerSecond,  (&fps),
                         ADTA_KeyFrame,         (&keyframe),
                         TAG_DONE ) == 11UL )
      {
        struct TagItem     *tstate,
                           *ti;

        numframes -= startframe;

        tstate = dtw -> dtw_AttrList;

        while( ti = NextTagItem( (&tstate) ) )
        {
          switch( ti -> ti_Tag )
          {
            case ADTA_Frame:            startframe = ti -> ti_Data; break;
            case ADTA_Frames:           numframes  = ti -> ti_Data; break;
            case ADTA_FrameIncrement:   framestep  = ti -> ti_Data; break;
          }
        }

        if( framestep == 0UL ) framestep = 1UL;

        verbose_printf( cb, gaid, "saving gif anim %lu %lu %lu\n", startframe, numframes, framestep );
        
        /* here should follow the encoder part... */
      }
      else
      {
        error_printf( cb, gaid, "not enougth attributes\n" );
      }
    }
#else
    error = ERROR_NOT_IMPLEMENTED; /* no encoder yet */
#endif /* HAS_ENCODER */

    SetIoErr( error );

    return( retval );
}


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

    return( (BOOL)(!ReadOK( (gifdec -> file), color, (sizeof( struct ColorRegister ) * numcolors) )) );
}


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

    switch( label )
    {
      case 0x01:              /* Plain Text Extension */
      {
          str = "Plain Text Extension";

#ifdef COMMENTED_OUT
          if( GetDataBlock( cb, gaid, buf ) == 0 )
            ;

          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 ];

          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 ] );
#endif

            index++;
          }

          return;
#else
          error_printf( cb, gaid, "'Plain text extension' ignored. Please send this animation to the author that"
                                  "this can be implemented\n" );
#endif /* COMMENTED_OUT */
      }
          break;

      case 0xf9:              /* Graphic Control Extension */
      {
          (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 ] & 0x1) != 0 )
            gifdec -> Gif89 . transparent = buf[ 3 ];

          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 );
            }
          }

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

          verbose_printf( cb, gaid, "gif comment: '%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 )
{
    UBYTE              count;
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

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

      return( -1 );
    }

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

    if( (count != 0) && (!ReadOK( (gifdec -> file), buf, 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;
    UBYTE              count;
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

    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;

      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 ][ 0 ] = 0;

      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, struct ColorRegister *color, BOOL interlace, BOOL ignore )
{
    UBYTE              c;
    struct GIFDecoder *gifdec = (&(gaid -> gaid_GIFDec));

    /* Initialize the Compression routines */
    if( !ReadOK( (gifdec -> file), &c, 1 ) )
      D( kprintf( "EOF / read error on image data\n" ) );

    /* 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( (gifdec -> file), &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
    {
      int v;
      int xpos = 0,
          ypos = 0,
          pass = 0;

      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( ((gifdec -> Gif89 . transparent) == ~0U) ||
            ((gifdec -> Gif89 . transparent) != v) )
        {
          /* Store pixel */
          image[ ((ypos + top) * imagewidth) + (xpos + left) ] = v;
        }

        xpos++;

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

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

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

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

        if( ypos >= height )
          break;
      }

fini:
      if( LWZReadByte( cb, gaid, FALSE, c ) >= 0 )
        verbose_printf( cb, gaid, "too much input data, ignoring extra...\n" );
    }
}


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

#if 0 /* only for IFF ANIM animations */
    /* An interleave of 0 means two frames back */
    if( interleave == 0UL )
    {
      interleave = 2UL;
    }
#endif

    /* 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 WritePixelArray8Fast( struct BitMap *dest, UBYTE *source, UBYTE *prev )
{
    ULONG *plane[ 8 ] = { 0 },
          *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)

    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 );
}


#if 0
static
void ReadGIF( struct GIFDecoder *gifdec )
{
    UBYTE                buf[ 16 ];
    UBYTE                c;
    struct ColorRegister localColorMap[ MAXCOLORMAPSIZE ];
    BOOL                 useGlobalColormap;
    UWORD                bitPixel;
    UBYTE                version[ 4 ];
    ULONG                imagecount = 0UL;

    if( !ReadOK( (gifdec -> file), buf, 6 ) )
      pm_error( "error reading magic number" );

    if( strncmp( (char *)buf, "GIF", 3 ) != 0 )
      pm_error( "not a GIF file" );

    strncpy( version, (char *)(buf + 3), 3 );
    version[ 3 ] = '\0';

    if( (strcmp( version, "87a" ) != 0) &&
        (strcmp( version, "89a" ) != 0) )
    {
      pm_error( "bad version number, not '87a' or '89a'" );
    }

    if( !ReadOK( (gifdec -> file), buf, 7 ) )
    {
      pm_error( "failed to read screen descriptor" );
    }

    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 ];

    /* Global Colormap ? */
    if( BitSet( buf[ 4 ], LOCALCOLORMAP ) )
    {
      if( ReadColorMap( cb, gifdec, (gifdec -> GifScreen . BitPixel), (gifdec -> GifScreen . ColorMap) ) )
      {
        pm_error( "error reading global colormap" );
      }
    }

#ifdef COMMENTED_OUT
    if( (gifdec -> GifScreen . AspectRatio != 0) && (GifScreen . AspectRatio != 49) )
    {
      float r;

      r = ((float)(gifdec -> GifScreen . AspectRatio) + 15.0) / 64.0;
      Printf( "warning - non-square pixels; to fix do a 'pnmscale -%cscale %g'\n", (r < 1.0)?('x'):('y'), (r < 1.0)?(1.0 / r):(r) );
    }
#endif /* COMMENTED_OUT */

    for( ;; )
    {
      if( !ReadOK( (gifdec -> file), (&c), 1 ) )
        pm_error( "EOF / read error on image data" );

      switch( c )
      {
        case ';': /* GIF terminator ? */
            return;

        case '!': /* Extension ? */
        {
            if( !ReadOK( (gifdec -> file), &c,1 ) )
              pm_error("OF / read error on extention function code");

            DoExtension( cb, gifdec, c );
        }
            break;

        case ',': /* Raster data start ? */
        {
            if( !ReadOK( (gifdec -> file), buf, 9 ) )
              pm_error( "couldn't read left/top/width/height" );

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

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

            if( !useGlobalColormap )
            {
              if( ReadColorMap( cb, gifdec, bitPixel, localColorMap ) )
                pm_error( "error reading local colormap" );

              ReadImage( gifdec,
                         LOHI2UINT16( buf[ 0 ], buf[ 1 ] ),
                         LOHI2UINT16( buf[ 2 ], buf[ 3 ] ),
                         LOHI2UINT16( buf[ 4 ], buf[ 5 ] ),
                         LOHI2UINT16( buf[ 6 ], buf[ 7 ] ),
                         localColorMap,
                         BitSet( buf[ 8 ], INTERLACE ),
                         (BOOL)(imagecount % 2) );
            }
            else
            {
              ReadImage( gifdec,
                         LOHI2UINT16( buf[ 0 ], buf[ 1 ] ),
                         LOHI2UINT16( buf[ 2 ], buf[ 3 ] ),
                         LOHI2UINT16( buf[ 4 ], buf[ 5 ] ),
                         LOHI2UINT16( buf[ 6 ], buf[ 7 ] ),
                         (gifdec -> GifScreen . ColorMap),
                         BitSet( buf[ 8 ], INTERLACE ),
                         (BOOL)(imagecount % 2) );
            }

            if( ((gifdec -> Gif89 . delayTime) != ~0U) && ((gifdec -> Gif89 . delayTime) > 0U) )
            {
              D( kprintf( "delay %lu\n", (ULONG)(gifdec -> Gif89 . delayTime) ) );

              if( (gifdec -> Gif89 . delayTime) < 2000 )
              {
                Delay( (gifdec -> Gif89 . delayTime) / (100L / TICKS_PER_SECOND) );
              }
            }

            imagecount++;
        }
            break;

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


/* translate IBM PC character set to ISO Latin1. Limmited to 7 bit chars, except some german specific */
static
void IBMPC2ISOLatin1( STRPTR ibmpc, STRPTR isoloatin1 )
{
    /* the following must be unsigned */
    register UBYTE *from = (UBYTE *)ibmpc,
                   *to   = (UBYTE *)isoloatin1;

    if( from && to )
    {
      do
      {
        register UBYTE ch = *from;

        switch( ch )
        {
          case 132: *(to++) = 'ä'; break;
          case 148: *(to++) = 'ö'; break;
          case 129: *(to++) = 'ü'; break;
          case 142: *(to++) = 'Ä'; break;
          case 153: *(to++) = 'Ö'; break;
          case 154: *(to++) = 'Ü'; break;
          case 225: *(to++) = 'ß'; break;
          case   9:                       /* tab */
          case  10: break;                /* ignore lf to filter ctrl/lf-sequences */
          default:
          {
              if( ch < 128 )
              {
                *to++ = ch;
              }
              else
              {
                *to++ = '_'; /* can't convert */
              }
          }
              break;
        }
      } while( *from++ );
    }
}


