/***************************************************************************
 * pic_pak.c   - "PicPak" - IFF ILBM picture manipulation functions        *
 *                (c) 1990 Videoworks Computer Applications                *
 *                All rights reserved.                                     *
 *                                                                         *
 *                Written by Paul T. Miller                                *
 *                                                                         *
 * DISCLAIMER:                                                             *
 * Feel free to use these routines or derivatives in your own code, but    *
 * please leave credit in your documentation. This code is NOT public      *
 * domain, and I am only allowing use of it because I'm a nice guy and I   *
 * want to show people how to effectively use their code. If you make any  *
 * modifications or enhancements to this code, please let me know.         *
 *                                                                         *
 * Send comments/suggestions to:                                           *
 * Paul Miller                                                             *
 * The MBT, Coconut Telegraph, The TARDIS                                  *
 *                                                                         *
 * Program Name:  N/A                                                      *
 * Version:       1                                                        *
 * Revision:      3                                                        *
 *-------------------------------------------------------------------------*
 * File: (pic_pak.c) IFF package routines                                  *
 *-------------------------------------------------------------------------*
 * Modification History                                                    *
 * Date     Author   Comment                                               *
 * -------- ------   -------                                               *
 * 10-02-90    PTM   Created. Pic allocation/freeing with specified memtype
 * 10-03-90    PTM   iff.library hooks
 * 10-08-90    PTM   clean up memory allocation
 * 11-28-90    PTM   SetImageType(), custom processing of non-standard forms
 *                   ViewPort fade/color set functions, color-cycling
 * 12-01-90    PTM   SHAM Chunk/ColorMap support - improve file parsing
 * 12-02-90    PTM   SHAM Copper-list code, ViewPort support code
 * 12-06-90    PTM   Add image decompression buffer
 * 12-07-90    PTM   Free mask plane if present in a Frame BitMap
 * 10-13-91    PTM   re-prototype
 ***************************************************************************/

#ifndef PIC_PAK_H
#include "pic_pak.h"
#endif

extern struct Custom far custom;
extern struct GfxBase *GfxBase;
extern struct IntuitionBase *IntuitionBase;
extern struct Library *DOSBase;

/* Some IFF parsing structs/constants */
#define BUFFER_SIZE  32000                /* image decompression buffer */

#define MAKE_ID(a,b,c,d) (((long)(a)<<24)|((long)(b)<<16)|((long)(c)<<8)|(long)(d))

/*  IFF types we may encounter  */
#define FORM   MAKE_ID('F', 'O', 'R', 'M')
#define ILBM   MAKE_ID('I', 'L', 'B', 'M')
#define BMHD   MAKE_ID('B', 'M', 'H', 'D')
#define CMAP   MAKE_ID('C', 'M', 'A', 'P')
#define BODY   MAKE_ID('B', 'O', 'D', 'Y')
#define CAMG   MAKE_ID('C', 'A', 'M', 'G')
#define CRNG   MAKE_ID('C', 'R', 'N', 'G')
#define SHAM   MAKE_ID('S', 'H', 'A', 'M')
#define DYCP   MAKE_ID('D', 'Y', 'C', 'P')
#define CTBL   MAKE_ID('C', 'T', 'B', 'L')
#define GRAB   MAKE_ID('G', 'R', 'A', 'B')
#define SPRT   MAKE_ID('S', 'P', 'R', 'T')
#define CCRT   MAKE_ID('C', 'C', 'R', 'T')
#define DPPV   MAKE_ID('D', 'P', 'P', 'V')
#define DPPS   MAKE_ID('D', 'P', 'P', 'S')

#define byte(n) (((n + 15) >> 4) << 1)  /* Word aligned width in bytes. */

struct BitMapHeader {
   UWORD w,h;           /* Raster width & height. */
   UWORD x,y;           /* Pixel position. */
   UBYTE nPlanes;       /* Number of source bitplanes. */
   UBYTE masking;       /* Masking... good for nothing maybe? */
   UBYTE compression;   /* Packed or unpacked? */
   UBYTE pad1;          /* We don't like odd length structures. */
   UWORD transparentColor; /* Maybe good for... */
   UBYTE xAspect, yAspect; /* Kind of quotient, width / height. */
   WORD pageWidth, pageHeight;   /* Source page size. */
};

/* IFF Stuff */
union typekludge {
   char type_str[4];
   long type_long;
};
struct ChunkHeader {
   union typekludge chunktype;
   long chunksize;
};
#define TYPE            chunktype.type_long
#define STRTYPE         chunktype.type_str

/*  Masking techniques  */
#define mskNone                 0
#define mskHasMask              1
#define mskHasTransparentColor  2
#define mskLasso                3

/*  Compression techniques  */
#define cmpNone                 0
#define cmpByteRun1             1

/* color-cycling stuff */
#define CRNG_NORATE     36      /* Don't cycle this range. */
#define CRNG_ACTIVE     1 << 0  /* This range is active. */
#define CRNG_REVERSE    1 << 1  /* This range is cycling backwards. */

/* GraphiCraft private cycling chunk. */
struct CcrtChunk {
   WORD  direction;
   UBYTE start;
   UBYTE end;
   LONG  seconds;
   LONG  microseconds;
   WORD  pad;
};

/* some color-cycling globals (used only if color-cycling is enabled) */
struct CycleData *cdata = NULL;
int i, j;                     /* general-purpose indexes used by cycler */

UWORD fade_delay = 0;
ULONG max_bufsize = BUFFER_SIZE;

struct Pic *AllocatePic(UWORD width, UWORD height, UBYTE depth, UBYTE memtype)
{
   struct Pic *pic;
   struct BitMap *bitmap;
   ULONG i, size, memsize;

   if (memtype == NULL)
      memtype = MEMTYPE_CHIP;

   pic = (struct Pic *)AllocMem(PIC_SIZE, MEMF_CLEAR);
   if (!pic) return(NULL);

   bitmap = &pic->BitMap;

   InitBitMap(bitmap, (long)depth, (long)width, (long)height);

   pic->Width = width;
   pic->Height = height;
   pic->Depth = depth;
   pic->Colors = 1<<depth;

   size = bitmap->BytesPerRow * bitmap->Rows;
   memsize = size * (ULONG)depth;

   if (memtype != MEMTYPE_NONE)
   {
      /* attempt to allocate a chunk of the required memory size and type */
      if (memtype == MEMTYPE_ANY)      /* if ANY type, try CHIP first */
      {
         bitmap->Planes[0] = (PLANEPTR)AllocMem(memsize, MEMF_CHIP);
         if (!bitmap->Planes[0])
            memtype = MEMTYPE_FAST;    /* out of CHIP, try for FAST */
      }

      if (memtype == MEMTYPE_CHIP)
         bitmap->Planes[0] = (PLANEPTR)AllocMem(memsize, MEMF_CHIP);

      if (memtype == MEMTYPE_FAST)
         bitmap->Planes[0] = (PLANEPTR)AllocMem(memsize, MEMF_FAST);

      if (!bitmap->Planes[0])       /* couldn't get the memory */
      {
         FreePic(pic);
         return(NULL);
      }
      for (i = 1; i < depth; i++)      /* set the plane pointers */
         bitmap->Planes[i] = (PLANEPTR)(bitmap->Planes[0] + (i * size));
   }
   if (memtype == MEMTYPE_ANY)   /* actually got CHIP ram */
      memtype = MEMTYPE_CHIP;

   pic->Memtype = memtype;

   return(pic);
}

void FreePic(struct Pic *pic)
{
   ULONG size, chunksize;
   struct BitMap *bm;

   if (pic)
   {
      if (bm = &pic->BitMap)
      {
         size = bm->BytesPerRow * bm->Rows;
         chunksize = size * (ULONG)bm->Depth;

         if (bm->Planes[0])
            FreeMem(bm->Planes[0], chunksize);
      }
      if (pic->PicExt)
      {
         switch (pic->Type)
         {
            case PICTYPE_SHAM:
               FreeMem(pic->PicExt, SHAMDATA_SIZE);
               break;
            case PICTYPE_DHIRES:
               size = pic->Height * DHIRES_COLORS * sizeof(WORD);
               FreeMem(pic->PicExt, size);
               break;
         }
      }
      FreeMem(pic, PIC_SIZE);
   }
}

struct Frame *AllocateFrame(UWORD width, UWORD height, UBYTE depth)
{
   struct Frame *frame;
   struct BitMap *bitmap;
   ULONG i, size, memsize;

   frame = (struct Frame *)AllocMem(FRAME_SIZE, MEMF_CLEAR);
   if (!frame) return(NULL);

   bitmap = &frame->BitMap;

   InitBitMap(bitmap, (long)depth, (long)width, (long)height);

   frame->Width = width;
   frame->Height = height;

   size = bitmap->BytesPerRow * bitmap->Rows;
   memsize = size * (ULONG)depth;

   bitmap->Planes[0] = (PLANEPTR)AllocMem(memsize, MEMF_CHIP);
   if (!bitmap->Planes[0])
   {
      FreeMem(frame, FRAME_SIZE);
      return(NULL);
   }
   for (i = 1; i < depth; i++)      /* set the plane pointers */
      bitmap->Planes[i] = (PLANEPTR)(bitmap->Planes[0] + (i * size));

   return(frame);
}

void FreeFrame(struct Frame *frame)
{
   ULONG size, chunksize;
   struct BitMap *bm;

   if (frame)
   {
      if (bm = &frame->BitMap)
      {
         size = RASSIZE(bm->BytesPerRow * 8, bm->Rows);
         chunksize = size * (ULONG)bm->Depth;

         if (bm->Planes[0])
            FreeMem(bm->Planes[0], chunksize);
         if (bm->Planes[bm->Depth])                /* extra mask plane */
            FreeMem(bm->Planes[bm->Depth], size);
      }
      FreeMem(frame, FRAME_SIZE);
   }
}

struct Pic *LoadPic(STRPTR filename, UBYTE memtype)
{
   return(LoadImage(filename, NULL, PICTYPE_NORMAL, memtype));
}

struct Pic *LoadPic2BitMap(STRPTR filename, struct BitMap *bitmap)
{
   return(LoadImage(filename, bitmap, PICTYPE_NORMAL, MEMTYPE_NONE));
}

struct Frame *LoadFrame(STRPTR filename)
{
   return((struct Frame *)LoadImage(filename, NULL, PICTYPE_FRAME, NULL));
}

struct Pic *LoadImage(STRPTR filename, struct BitMap *bitmap, UBYTE type, UBYTE memtype)
{
   struct Pic *pic = NULL;
   struct Frame *frame = NULL;
   struct BitMapHeader bmhd;
   struct ChunkHeader ch;
   FILE *file;
   LONG formsize, subtype;
   UWORD version;
   ULONG size;

   file = fopen(filename, "r");
   if (!file)
      return(NULL);

   if (!fread((char *)&ch, sizeof(struct ChunkHeader), 1, file))
   {
      fclose(file);
      return(NULL);
   }
   if (ch.TYPE != FORM)    /* not an IFF file */
   {
      fclose(file);
      return(NULL);
   }

   if (!fread((char *)&subtype, sizeof(subtype), 1, file))
   {
      fclose(file);
      return(NULL);
   }
   formsize = ch.chunksize - sizeof(subtype);
   if (subtype != ILBM)    /* not an ILBM image */
   {
      fclose(file);
      return(NULL);
   }

   while (formsize > 0)
   {
      if (!fread((char *)&ch, sizeof(struct ChunkHeader), 1, file))
      {
         fclose(file);
         if (pic) FreePic(pic);
         if (frame) FreeFrame(frame);
         return(NULL);
      }
      formsize -= sizeof(struct ChunkHeader);

      switch (ch.TYPE)
      {
         case BMHD:
            fread((char *)&bmhd, ch.chunksize, 1, file);

            /* some error checking */
            if (bmhd.nPlanes < 1 || bmhd.nPlanes > 8)
            {
               fclose(file);
               return(NULL);
            }
            if (bitmap)          /* application has supplied a BitMap */
               memtype = MEMTYPE_NONE;

            if (type == PICTYPE_FRAME)
            {
               frame = AllocateFrame(bmhd.w, bmhd.h, bmhd.nPlanes);
               if (!frame)
               {
                  fclose(file);
                  return(NULL);
               }
               frame->X = bmhd.w;
               frame->Y = bmhd.h;
            }
            else
            {
               pic = AllocatePic(bmhd.w, bmhd.h, bmhd.nPlanes, memtype);
               if (!pic)
               {
                  fclose(file);
                  return(NULL);
               }
            }
            break;
         case CMAP:
            if (type == PICTYPE_FRAME)
            {
               fseek(file, ch.chunksize, 1);
               break;
            }
            pic->Colors = LoadCMAP(file, ch.chunksize, &pic->Colormap[0]);
            break;
         case CAMG:
            if (type == PICTYPE_FRAME)
            {
               fseek(file, ch.chunksize, 1);
               break;
            }
            fread((char *)&subtype, sizeof(subtype), 1, file);
            pic->ViewModes = (ULONG)subtype;
            if ((UWORD)pic->ViewModes == NULL)
               pic->ViewModes >>= 16L;
            pic->ViewModes &= ~(SPRITES|VP_HIDE|PFBA|GENLOCK_AUDIO|GENLOCK_VIDEO);
            break;
         case CRNG:
         case CCRT:
            if (type == PICTYPE_FRAME || pic->Cycles == MAXCRANGES)
            {
               fseek(file, ch.chunksize, 1);
               break;
            }
            LoadCycleRange(file, &pic->CRanges[pic->Cycles], ch.TYPE);
            pic->Cycles++;
            break;
         case SHAM:
            if (type == PICTYPE_FRAME)
            {
               fseek(file, ch.chunksize, 1);
               break;
            }
            pic->PicExt = (APTR)AllocMem(SHAMDATA_SIZE, NULL);
            if (!pic->PicExt)
               fseek(file, ch.chunksize, 1);
            else
            {
               fread((char *)&version, sizeof(UWORD), 1, file);
               ch.chunksize -= sizeof(UWORD);
               if (version == 0)
               {
                  fread((char *)pic->PicExt, ch.chunksize, 1, file);
                  pic->Type = PICTYPE_SHAM;
               }
               else
                  fseek(file, ch.chunksize, 1);
            }
            break;
         case CTBL:
            if (type == PICTYPE_FRAME)
            {
               fseek(file, ch.chunksize, 1);
               break;
            }
            size = pic->Height * DHIRES_COLORS * sizeof(WORD);
            pic->PicExt = (APTR)AllocMem(size, NULL);
            if (!pic->PicExt)
               fseek(file, ch.chunksize, 1);
            else
            {
               fread((char *)pic->PicExt, ch.chunksize, 1, file);
               pic->Type = PICTYPE_DHIRES;
            }
            break;
         case BODY:
            if (type == PICTYPE_FRAME && frame)
            {
               if (!LoadRaster(file, ch.chunksize, &frame->BitMap.Planes[0], &bmhd))
               {
                  fclose(file);
                  FreeFrame(frame);
                  return(NULL);
               }
            }
            if (type != PICTYPE_FRAME)
            {
               if (!bitmap)
                  bitmap = &pic->BitMap;
               if (!LoadRaster(file, ch.chunksize, &bitmap->Planes[0], &bmhd))
               {
                  fclose(file);
                  FreePic(pic);
                  return(NULL);
               }
            }
            /* make sure the BODY isn't off a few bytes */
            if (formsize - ch.chunksize > 0)
               ch.chunksize += (formsize - ch.chunksize);
            break;
         default:
            fseek(file, ch.chunksize, 1);
            break;
      }
      formsize -= ch.chunksize;
      if (ch.chunksize & 1)         /* odd-length chunk */
      {
         formsize--;
         fseek(file, 1L, 1);
      }
   }
   fclose(file);

   if (type == PICTYPE_FRAME)
      return((struct Pic *)frame);

   SetImageType(pic);      /* perform any post-processing/identification */

   /* now double-check the display size flags */
   if (bmhd.pageHeight > 242)
      pic->ViewModes |= LACE;
   if (bmhd.pageWidth >= 640)
      pic->ViewModes |= HIRES;

   return(pic);
}

/* GetPicAttrs()
      - return the specified picture's size, depth, and viewmode information
        in supplied pointer variables
*/

GetPicAttrs(STRPTR filename, WORD *w, WORD *h, WORD *d, ULONG *vm)
{
   struct Pic *pic;

   /* load just the header information */
   pic = LoadImage(filename, NULL, NULL, MEMTYPE_NONE);
   if (pic)
   {
      *w = pic->Width;
      *h = pic->Height;
      *d = pic->Depth;
      *vm = pic->ViewModes;
      FreePic(pic);
      return(1);
   }
   return(NULL);
}

/* Pic2BitMap()
      - download Pic imagery stored in FAST RAM to the specified BitMap
*/
Pic2BitMap(struct Pic *pic, struct BitMap *bm)
{
   ULONG i, size;

   if (pic && bm)
   {
      if (pic->Memtype == MEMTYPE_CHIP)
      {
         BltBitMap(&pic->BitMap, 0, 0, bm, 0, 0, pic->Width,pic->Height,
                   0xc0, 0xff, NULL);
         return(1);
      }

      if (pic->Depth != bm->Depth)
         return(NULL);
      if (pic->Width != bm->BytesPerRow * 8)
         return(NULL);
      if (pic->Height != bm->Rows)
         return(NULL);

      size = pic->BitMap.BytesPerRow * pic->BitMap.Rows;
      for (i = 0; i < bm->Depth; i++)
         memcpy(bm->Planes[i], pic->BitMap.Planes[i], size);

      return(1);
   }
   return(NULL);
}

BOOL LoadRaster(FILE *file, ULONG chunksize, PLANEPTR *BitPlanes, struct BitMapHeader *BMHeader)
{
   register LONG i, j, k;
   register BYTE ChkVal;
   register UBYTE Value, SoFar;
   UBYTE Compr, Depth;
   LONG Height, Width;
   PLANEPTR Planes[9];    /* 9 for possible bitmask. */
   UBYTE *memory = NULL;

   for(i = 0; i < 9; i++)
      Planes[i] = NULL;

   Width   = byte(BMHeader->w);
   Height  = BMHeader->h;
   Depth   = BMHeader->nPlanes;
   Compr   = BMHeader->compression;

   if (Compr > cmpByteRun1 || !BitPlanes)
      return(FALSE);

   for(i = 0; i < Depth; i++)
      Planes[i] = BitPlanes[i];

   if (BMHeader->masking == mskHasMask)
      Depth++;

   if (Compr == cmpNone)
   {
      for (k = 0; k < Height; k++)
      {
         for (j = 0; j < Depth; j++)
         {
            if (Planes[j])
            {
               fread((char *)Planes[j], Width, 1, file);
               Planes[j] += Width;
            }
            else
               fseek(file, Width, 1);
         }
      }
   }
   if (Compr == cmpByteRun1)
   {
      if (chunksize < max_bufsize)
      {
         memory = (UBYTE *)AllocMem(chunksize, NULL);
         if (memory)
         {
            fread(memory, chunksize, 1, file);
            mem_decompress(memory, &Planes[0], Width, Height, Depth);
            FreeMem(memory, chunksize);
            return(1);
         }
      }
      for (k = 0; k < Height; k++)
      {
         for (j = 0; j < Depth; j++)
         {
            for (SoFar = 0; SoFar < Width ;)
            {
               ChkVal = fgetc(file);

               if (ChkVal > 0)
               {
                  if (Planes[j])
                  {
                     fread(Planes[j], ChkVal + 1, 1, file);
                     Planes[j] += ChkVal + 1;
                  }
                  else
                     fseek(file, ChkVal + 1, 1);

                  SoFar += ChkVal + 1;
               }
               else
               {
                  if (ChkVal != 128)
                  {
                     Value = fgetc(file);

                     for (i = 0; i <= -ChkVal; i++)
                     {
                        if (Planes[j])
                        {
                           *Planes[j] = Value;
                           Planes[j]++;
                        }
                        SoFar++;
                     }
                  }
               }
            }
         }
      }
   }
   return(TRUE);
}

void mem_decompress(register UBYTE *ptr, PLANEPTR *Planes, LONG Width, LONG Height, UBYTE Depth)
{
   LONG i, j, k;
   register BYTE ChkVal;
   UBYTE Value, SoFar;

   for (k = 0; k < Height; k++)
   {
      for (j = 0; j < Depth; j++)
      {
         for (SoFar = 0; SoFar < Width ;)
         {
            ChkVal = *ptr;
            ptr++;

            if (ChkVal > 0)
            {
               if (Planes[j])
               {
                  memcpy(Planes[j], ptr, ChkVal+1);
                  Planes[j] += ChkVal + 1;
                  ptr += ChkVal + 1;
               }
               else
                  ptr += ChkVal + 1;

               SoFar += ChkVal + 1;
            }
            else
            {
               if (ChkVal != 128)
               {
                  Value = *ptr;
                  ptr++;

                  for (i = 0; i <= -ChkVal; i++)
                  {
                     if (Planes[j])
                     {
                        *Planes[j] = Value;
                        Planes[j]++;
                     }
                     SoFar++;
                  }
               }
            }
         }
      }
   }
}

void SetPicReadBufSize(ULONG size)
{
   max_bufsize = size;
}

UWORD LoadCMAP(FILE *file, LONG csize, UWORD *cmap)
{
   UBYTE *ctable;
   UWORD i, n;
   LONG len;

   len = csize;
   if (len > (MAXCOLORS * 3))
      len = MAXCOLORS * 3;

   ctable = (UBYTE *)AllocMem(csize, NULL);
   if (!ctable)
      return(NULL);

   fread((char *)ctable, csize, 1, file);

   for (i = n = 0; n < len; i++, n += 3)
      cmap[i] = ((ctable[n]>>4)<<8)+((ctable[n+1]>>4)<<4)+(ctable[n+2]>>4);

   FreeMem(ctable, csize);

   return(i);
}

void LoadCycleRange(FILE *file, CRange *range, LONG type)
{
   struct CcrtChunk crtchunk;

   switch (type)
   {
      case CRNG:
         fread((char *)range, sizeof(CRange), 1, file);

         /* Carefully determine the activity of the chunk. */
         if (range->active == CRNG_NORATE || !range->rate || range->low == range->high)
            range->active = 0;

         /* Recalculate speed value. */
         if (range->rate > 0)
            range->rate = 16384 / range->rate;
         else
            range->rate = 0;
         break;
      case CCRT:
         fread((char *)&crtchunk, sizeof(struct CcrtChunk), 1, file);

         /* We have located a CCRT chunk, now make it a CRNG chunk. */
         range->low  = crtchunk.start;
         range->high = crtchunk.end;

         if (crtchunk.direction != 0)
            range->active = CRNG_ACTIVE;
         else
            range->active = 0;

         if (crtchunk.direction > 0)
            range->active |= CRNG_REVERSE;

         /* Recalculate speed (by Carolyn Scheppner). */
         range->rate = 16384 / (crtchunk.seconds * 60 +
                         (crtchunk.microseconds + 8334) / 16667);
         if (!range->rate || range->low == range->high)
            range->active = 0;
         if (range->rate > 0)
            range->rate = 16384 / range->rate;
         else
            range->rate = 0;
         break;
   }
}

void SetImageType(struct Pic *pic)
{
   /* handle custom processing of non-standard picture types */
   if (pic->Type)    /* already set */
   {
      switch (pic->Type)
      {
         case PICTYPE_SHAM:
            pic->Colors = 16;
            pic->ViewModes |= HAM;
            break;
      }
      return;
   }

   if (pic->ViewModes & HAM)
   {
      pic->Colors = 16;
      pic->Type = PICTYPE_HAM;
      return;
   }
   if (pic->Width <= 320 && pic->Height <= 200 && pic->Depth == 6)
   {
      pic->Colors = MAXCOLORS;
      pic->ViewModes = EXTRA_HALFBRITE;
      pic->Type = PICTYPE_EHB;
      return;
   }
   pic->Type = PICTYPE_NORMAL;
}

/**********************************************************/
/* GENERAL-PURPOSE PICTURE/VIEWPORT MANIPULATION ROUTINES */
/**********************************************************/

/*****************/
/* COLOR-CYCLING */
/*****************/

struct CycleData {
   struct ViewPort *vp;
   struct Pic *pic;
   struct Interrupt *vblank;
   UBYTE cycling;
   UBYTE pad;
   UWORD temp_col;
   UWORD cmap[MAXCOLORS];
   WORD  ticks[16];
};

typedef void   (*FPTR)();

InitCycler()
{
   cdata = (struct CycleData *)AllocMem(sizeof(struct CycleData), MEMF_CLEAR);
   if (!cdata)
      return(NULL);

   cdata->vblank = (struct Interrupt *)AllocMem(sizeof(struct Interrupt), MEMF_CLEAR);
   if (!cdata->vblank)
      return(NULL);

   cdata->vblank->is_Data = NULL;
   cdata->vblank->is_Code = (FPTR)cycle;
   cdata->vblank->is_Node.ln_Succ = NULL;
   cdata->vblank->is_Node.ln_Pred = NULL;
   cdata->vblank->is_Node.ln_Type = NT_INTERRUPT;
   cdata->vblank->is_Node.ln_Pri = 0;
   cdata->vblank->is_Node.ln_Name = "PicPak_Color_Cycler";

   AddIntServer(INTB_VERTB, cdata->vblank);

   return(1);
}

void FreeCycler()
{
   if (cdata)
   {
      if (cdata->vblank)
      {
         StopCycling();

         RemIntServer(INTB_VERTB, cdata->vblank);
         FreeMem(cdata->vblank, sizeof(struct Interrupt));
         WaitTOF();
         WaitTOF();
         WaitTOF();
      }
      FreeMem(cdata, sizeof(struct CycleData));
   }
}

__saveds void cycle()
{
   if (cdata->cycling)
   {
      for (i = 0; i < cdata->pic->Cycles; i++)
      {
         /* Increment event counter. */
         cdata->ticks[i]++;

         /* Is this one up to cycle next? */
         if (cdata->ticks[i] == cdata->pic->CRanges[i].rate)
         {
            /* Reset event counter for this range. */

            cdata->ticks[i] = 0;

            /* Is this range active? */

            if (!(cdata->pic->CRanges[i].active & CRNG_ACTIVE))
               continue;

            /* Cycling backwards? */
            if(cdata->pic->CRanges[i].active & CRNG_REVERSE)
            {
               /* Move the colours. */
               cdata->temp_col = cdata->cmap[cdata->pic->CRanges[i].low];

               for(j = cdata->pic->CRanges[i].low; j < cdata->pic->CRanges[i].high; j++)
                  cdata->cmap[j] = cdata->cmap[j+1];

               cdata->cmap[cdata->pic->CRanges[i].high] = cdata->temp_col;
            }
            else
            {
               /* This one is cycling forwards. */

               cdata->temp_col = cdata->cmap[cdata->pic->CRanges[i].high];

               for(j = cdata->pic->CRanges[i].high; j > cdata->pic->CRanges[i].low; j--)
                  cdata->cmap[j] = cdata->cmap[j-1];

               cdata->cmap[cdata->pic->CRanges[i].low] = cdata->temp_col;
            }
            /* Okay, everything has been moved, now load the new palette. */
            LoadRGB4(cdata->vp, cdata->cmap, cdata->pic->Colors);
         }
      }
   }
}

void StartCycling(struct ViewPort *vp, struct Pic *pic)
{
   if (cdata && pic->Cycles)
   {
      cdata->pic = pic;
      cdata->vp = vp;

      /* reset the palette and event counters */
      for (i = 0; i < pic->Colors; i++)
         cdata->cmap[i] = pic->Colormap[i];

      for (i = 0; i < pic->Cycles; i++)
         cdata->ticks[i] = 0;

      cdata->cycling = 1;
   }
}

void StopCycling()
{
   /* restore old colormap */
   if (cdata)
   {
      if (cdata->cycling)
         LoadRGB4(cdata->vp, &cdata->pic->Colormap[0], cdata->pic->Colors);

      cdata->cycling = NULL;
   }
}

void ToggleCycling()
{
   if (cdata)
   {
      if (cdata->cycling)
         StopCycling();
      else
         StartCycling(cdata->vp, cdata->pic);
   }
}

IsCycling()
{
   if (!cdata) return(NULL);
   return((int)cdata->cycling);
}

/***************************/
/* VIEWPORT-COLOR ROUTINES */
/***************************/

void SetViewPortPicColors(struct ViewPort *vp, struct Pic *pic)
{
   switch (pic->Type)
   {
      case PICTYPE_SHAM:
         InitSHAM(vp, pic);
         break;
      case PICTYPE_DHIRES:
         InitDHIRES(vp, pic);
         break;
      default:
         LoadRGB4(vp, &pic->Colormap[0], pic->Colors);
   }
}

void ClearViewPortColors(struct ViewPort *vp, UWORD colors)
{
   register int i;

   if (colors > MAXCOLORS)
      colors = MAXCOLORS;

   for (i = 0; i < colors; i++)
      SetRGB4(vp, i, 0, 0, 0);
}

#define RED(c)    ((c >> 8) & 0xF)
#define GREEN(c)  ((c >> 4) & 0xF)
#define BLUE(c)   (c & 0xF)

void FadeViewPortIn(struct ViewPort *vp, UWORD *cm, UWORD colors)
{
   ULONG rf[MAXCOLORS], gf[MAXCOLORS], bf[MAXCOLORS];
   register USHORT rgb, r, g, b;
   WORD i, n;

   if (colors > MAXCOLORS)
      colors = MAXCOLORS;

   for (i = 0; i < colors; i++)
   {
      rgb = cm[i];
      rf[i] = (RED(rgb) << 16) / 15;
      gf[i] = (GREEN(rgb) << 16) / 15;
      bf[i] = (BLUE(rgb) << 16) / 15;
   }
   for (n = 1; n < 16; n++)
   {
      for (i = 0; i < colors; i++)
      {
         r = (((rf[i] * n) + 0x8000) >> 16);
         g = (((gf[i] * n) + 0x8000) >> 16);
         b = (((bf[i] * n) + 0x8000) >> 16);

         SetRGB4(vp, i, r, g, b);
      }
      if (fade_delay > 0) Delay(fade_delay);
   }
}

void FadeViewPortOut(struct ViewPort *vp, UWORD colors)
{
   ULONG rf[MAXCOLORS], gf[MAXCOLORS], bf[MAXCOLORS];
   register UWORD rgb, r, g, b;
   WORD i, n;
   UWORD start = 0;

   if (GetRGB4(vp->ColorMap, 0) == 0x0000)
      start = 1;

   if (colors > MAXCOLORS)
      colors = MAXCOLORS;

   for (i = start; i < colors; i++)
   {
      rgb = GetRGB4(vp->ColorMap, i);
      rf[i] = (RED(rgb) << 16) / 15;
      gf[i] = (GREEN(rgb) << 16) / 15;
      bf[i] = (BLUE(rgb) << 16) / 15;
   }
   for (n = 14; n >= 0; n--)
   {
      for (i = start; i < colors; i++)
      {
         r = (((rf[i] * n) + 0x8000) >> 16);
         g = (((gf[i] * n) + 0x8000) >> 16);
         b = (((bf[i] * n) + 0x8000) >> 16);

         SetRGB4(vp, i, r, g, b);
      }
      if (fade_delay > 0) Delay(fade_delay);
   }
}

void SetFadeSpeed(UWORD val)
{
   fade_delay = val;
}

/* SPECIAL IMAGE TYPE INITIALIZATION CODE */

/* SHAM Copper-list initialization */
InitSHAM(struct ViewPort *vp, struct Pic *pic)
{
   register UWORD i, n;
   register struct SHAMData *sham;
   struct UCopList *ucoplist = NULL;
   BOOL lace = 0;
   register WORD view_dx = IntuitionBase->ViewLord.DxOffset;

   sham = (struct SHAMData *)pic->PicExt;
   if (!sham) return(NULL);

   SetRGB4(vp, 0, 0, 0, 0);
   for (i = 1; i < 16; i++)
      SetRGB4(vp, i, (UBYTE)(sham->ColorTable[0][i] / (UWORD)256),
                     (UBYTE)(sham->ColorTable[0][i] / (UWORD)16 % 16),
                     (UBYTE)(sham->ColorTable[0][i] % 16));

   ucoplist = (struct UCopList *)AllocMem(sizeof(struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);
   if (!ucoplist) return(NULL);

   if (pic->Height > LORES_HEIGHT)
      lace = 1;

   CINIT(ucoplist, (199*16));
   for (i = 1; i < 200; i++)
   {
      if (lace)
      {
         if (view_dx < 114)
         {
            CWAIT(ucoplist, i + i - 2, 0);
         }
         else
         {
            if (view_dx < 129)
            {
               CWAIT(ucoplist, i + i - 2, 0);
            }
            else
            {
               CWAIT(ucoplist, i + i, 0);
            }
         }
      }
      else
      {
         if (view_dx < 128)
         {
            CWAIT(ucoplist, i - 1, 0);
         }
         else
         {
            CWAIT(ucoplist, i, 0);
         }
      }
      for (n = 1; n < 16; n++)
         CMOVE(ucoplist, custom.color[n], (WORD)sham->ColorTable[i][n]);
   }
   CEND(ucoplist);

   Forbid();
   vp->UCopIns = ucoplist;
   Permit();
   RethinkDisplay();

   return(1);
}

/* Dynamic-HIRES Copper-list initialization */
InitDHIRES(struct ViewPort *vp, struct Pic *pic)
{
   register UWORD i, n;
   UWORD *ctbl;
   struct UCopList *ucoplist = NULL;
   BOOL lace = 0;

   ctbl = (UWORD *)pic->PicExt;
   if (!ctbl) return(NULL);

   ucoplist = (struct UCopList *)AllocMem(sizeof(struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);
   if (!ucoplist) return(NULL);

   if (pic->Height > LORES_HEIGHT)
      lace = 1;

   CINIT(ucoplist, (pic->Height * 16));
   for (i = 0; i < pic->Height; i++)
   {
      CWAIT(ucoplist, i, 0);
      for (n = 0; n < 16; n++)
         CMOVE(ucoplist, custom.color[n], (WORD)*(ctbl+(i*16)+n));
   }
   CEND(ucoplist);

   Forbid();
   vp->UCopIns = ucoplist;
   Permit();
   RethinkDisplay();

   return(1);
}

void FreeSHAM(struct ViewPort *vp)
{
   if (vp->UCopIns)
      FreeVPortCopLists(vp);
}
