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

  ilbm.c -- routines to read IFF.ILBM files written by Ray Lambert
  These routines are public domain

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

#include <stdio.h>
#include "keeper.h"
#include <exec/memory.h>
#include <hardware/blit.h>
#include <hardware/custom.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/graphics.h>


/*
**  IFF.ILBM stuff...
*/
struct BitMapHeader
{
  UWORD w,h;
  WORD  x,y;
  UBYTE nPlanes;
  UBYTE masking;
  UBYTE compression;
  UBYTE pad1;
  UWORD transparentColor;
  UBYTE xAspect,yAspect;
  WORD  pageWidth,pageHeight;
};

/*
**  compression types
*/
#define cmpNone      0
#define cmpByteRun1  1

/*
**  info to identify a mask
*/
#define mskNone                 0
#define mskHasMask              1
#define mskHasTransparentColor  2
#define mskLasso                3

/*
**  IFF.ILBM chunk names
*/
#define MakeID(a,b,c,d) ((a)<<24|(b)<<16|(c)<<8|(d))
#define FORM  MakeID('F','O','R','M')
#define ILBM  MakeID('I','L','B','M')
#define BMHD  MakeID('B','M','H','D')
#define CMAP  MakeID('C','M','A','P')
#define CAMG  MakeID('C','A','M','G')
#define BODY  MakeID('B','O','D','Y')



/*
**  Copy from 'src' to 'dst' -- 'dst' must receive 'rowlen' bytes --
**  bytes at 'src' may be compressed -- 'comp' indicates if compression
**  was used and if so what type -- returns the number of bytes read
**  from 'src' -- 'src' may be 0 and if so 'rowlen' zero bytes are placed
**  in 'dest' regardless of 'comp' -- 'dst' may be 0 and if so no bytes
**  are actually stored but 'src' is treated normally, allowing the caller
**  to determine how many 'src' bytes to skip in case 'src' is compressed
*/
static ULONG copy_line(UBYTE *src, UBYTE *dst, UBYTE comp, ULONG rowlen)
{
  register ULONG cnt;
  register signed char b;

/*
**  no source data - fill "dst" with "rowlen" zero bytes
*/
  unless( src )
    {
      memset(dst,0,rowlen);
      return(0);
    }

/*
**  no compression used - just do a direct copy
*/
  if (comp == cmpNone)
    {
      if (dst) memcpy(dst,src,rowlen);
      return(rowlen);
    }

/*
**  data is compressed -- expand "rowlen" number of bytes
**  "cnt" counts the number of bytes read from "src"
**  "rowlen" counts-down the number of bytes to put into "dst"
*/
  cnt = 0;
  while(rowlen > 0)
    {
      b = *src;
      src++;
      cnt++;
      if (b == -128) continue;

      if (b >= 0)
        {

        /*
        **  copy "b" + 1 bytes directly
        */
          b++;
          if (rowlen < b) b = rowlen;
          rowlen -= b;
          if (dst)
            {
              memcpy(dst,src,b);
              dst += b;
            }
          src += b;
          cnt += b;
        }
      else
        {

        /*
        **  replicate byte at "src" (abs(b) + 1) times
        */
          b = ( abs(b) + 1 );
          if (b > rowlen) b = rowlen;
          rowlen -= b;
          if (dst)
            {
              memset(dst,*src,b);
              dst += b;
            }
          src++;
          cnt++;
        }

    }

  return(cnt);
}



/*
**  Load the BODY data from an ILBM file
*/
static BOOL loadBODY(BPTR file, ULONG len, struct BitMapHeader *bmhd,
                      struct BLITmap *bm)
{
  ULONG
    rowlen,
    cnt,
    pcnt;
  PLANEPTR
    body,
    b,
    p[8];

/*
**  calculate number of bytes in one row
*/
  rowlen = ( ( (bmhd->w + 15) / 16 ) * 2 );

/*
**  allocate memory for each plane
*/
  for(pcnt = 0; pcnt < bmhd->nPlanes; pcnt++)
    unless( bm->p[pcnt] = p[pcnt] = AllocRaster(bmhd->w,bmhd->h) )
      return(FALSE);

/*
**  allocate memory to load body chunk into
*/
  unless( b = body = AllocMem(len,0) ) return(FALSE);

/*
**  read the BODY chunk into memory
*/
  unless( Read(file,body,len) == len )
    {
      FreeMem(body,len);
      return(FALSE);
    }

/*
**  expand interleaved BODY chunk into seperate planes
*/
  for(cnt = 0; cnt < bmhd->h; cnt++)
    {

    /*
    **  use "copy_line()" to expand a single line into each plane
    */
      for(pcnt = 0; pcnt < bmhd->nPlanes; pcnt++)
        {
          b += copy_line(b, p[pcnt], bmhd->compression, rowlen);
          p[pcnt] += rowlen;
        }

    /*
    **  skip the mask data line (if there is any)
    */
      if (bmhd->masking == mskHasMask)
        {
          b += copy_line(b, 0, bmhd->compression, rowlen);
        }

    }

/*
**  free the temporary buffer used for the BODY chunk
*/
  FreeMem(body,len);

/*
**  all's well that ends well
*/
  return(TRUE);
}



/*
**  load a CMAP chunk and pack it into an Amiga color map (palette)
*/
static BOOL loadCMAP(BPTR file, ULONG len, UWORD *cmap)
{
  UBYTE rgb[3*32];  /* buffer to read raw RGB bytes from file */
  ULONG cnt;

  cnt = min(len,sizeof(rgb)); /* don't load more than 'rgb' can hold */
  unless( Read(file,rgb,cnt) == cnt ) return(FALSE);
  if (len > cnt) Seek(file,(len-cnt),OFFSET_CURRENT); /* skip unused bytes */
  cnt /= 3; /* number of colors for CMAP (triplets of bytes in 'rgb') */
  PackCMAP(rgb,cmap,cnt);
  return(TRUE);
}



/*
**  load an IFF.ILBM picture from disk
*/
struct BLITmap *loadILBM(char *name)
{
  BPTR file;
  ULONG type, len;
  struct BitMapHeader bmhd;
  struct BLITmap *bm;

  unless( bm = (struct BLITmap *)AllocMem(sizeof(struct BLITmap),MEMF_CLEAR) )
    return(0);
  unless( file = Open(name,MODE_OLDFILE) )
    {
      FreeMem(bm,sizeof(struct BLITmap));
      return(0);
    }

  unless( (Read(file,(UBYTE *)&type,4) == 4) && (type == FORM) )
    {
enderr:
      Close(file);
      if (bm) unloadILBM(bm);
      return(0);
    }
  unless( Read(file,(UBYTE *)&len,4) == 4 ) goto enderr;  /* FORM length */
  unless( (Read(file,(UBYTE *)&type,4) == 4) && (type == ILBM) ) goto enderr;

  memset(bm->cmap,0,sizeof(bm->cmap));  /* clear the CMAP in case none in file */
  bm->modes = 0;  /* clear the modes in case none in file */
  bmhd.w = 0;     /* flag if a BMHD chunk has been found  */

  until(6==9)
    {
      unless( Read(file,(UBYTE *)&type,4) == 4) goto enderr; /* chunk type */
      unless( Read(file,(UBYTE *)&len,4) == 4) goto enderr;  /* chunk length */

      switch(type)
        {
          case BMHD:
            {
              unless( len == sizeof(struct BitMapHeader) ) goto enderr;
              unless( Read(file,(UBYTE *)&bmhd,len) == len ) goto enderr;
              bm->w = bmhd.w;
              bm->h = bmhd.h;
              bm->d = bmhd.nPlanes;
              bm->wpl = ( (bmhd.w + 15) / 16 );
              break;
            }
          case CMAP:
            {
              unless( loadCMAP(file,len,bm->cmap) ) goto enderr;
              break;
            }
          case CAMG:
            {
              unless( Read(file,(UBYTE *)&bm->modes,4) == 4 ) goto enderr;
              if (len > 4) Seek(file,(len-4),OFFSET_CURRENT);
              break;
            }
          case BODY:
            {
              goto doBODY;
            }
          default:  /* skip unused chunk */
            {
              Seek(file,len,OFFSET_CURRENT);
              break;
            }
        }

      if (len & 1) Seek(file,1,OFFSET_CURRENT);  /* odd sized chunk? */
    }

doBODY:
  unless( bmhd.w > 0 ) goto enderr;       /* no BMHD found? */
  unless( (bmhd.compression == cmpNone)   /* cmpression we don't support? */
  || (bmhd.compression == cmpByteRun1) ) goto enderr;
  unless( loadBODY(file,len,&bmhd,bm) ) goto enderr;

  Close(file);

  return(bm);
}



/*
**  Free all memory associated with a BLITmap, including all bitplanes,
**  the mask plane and the BLITmap structure itself.
*/
void unloadILBM(struct BLITmap *bm)
{
  register int i;
  if (bm)
    {
      for(i = 0; i < bm->d; i++)
        {
          if (bm->p[i]) FreeRaster(bm->p[i],bm->w,bm->h);
        }
      if (bm->m) FreeRaster(bm->m,bm->w,bm->h);
      FreeMem(bm,sizeof(struct BLITmap));
    }
}



/*
**  initialize a BitMap structure with info from a BLITmap structure
*/
void BLITmap2BitMap(struct BLITmap *a, struct BitMap *b)
{
  register int i;
  InitBitMap(b,a->d,a->w,a->h);
  for(i = 0; i < a->d; i++) b->Planes[i] = a->p[i];
}
