/* ilbmr.c --- ILBM loading routines for use with iffparse */

/*----------------------------------------------------------------------*
 * ILBMR.C  Support routines for reading ILBM files.
 * (IFF is Interchange Format File.)
 *
 * Based on code by Jerry Morrison and Steve Shaw, Electronic Arts.
 * This software is in the public domain.
 * Modified for iffparse.library 05/90
 * This version for the Commodore-Amiga computer.
 *----------------------------------------------------------------------*/

#include "iffp/ilbm.h"
#include "iffp/packer.h"
#include "iffp/ilbmapp.h"

#define movmem CopyMem

#define MaxSrcPlanes (25)

extern struct Library *GfxBase;

/*---------- loadbody ---------------------------------------------------*/

LONG loadbody(iff, bitmap, bmhd)
struct IFFHandle *iff;
struct BitMap *bitmap;
BitMapHeader *bmhd;
	{
	BYTE *buffer;
	ULONG bufsize;
	LONG error = 1;

	D(bug("In loadbody\n"));

	if(!(currentchunkis(iff,ID_ILBM,ID_BODY)))
	    {
	    message(SI(MSG_IFFP_NOBODY));	/* Maybe it's a palette */
	    return(IFF_OKAY);
	    }

	if((bitmap)&&(bmhd))
	    {
	    D(bug("Have bitmap and bmhd\n"));

	    bufsize = MaxPackedSize(RowBytes(bmhd->w)) << 4;
            if(!(buffer = AllocMem(bufsize,0L)))
		{
		D(bug("Buffer alloc of %ld failed\n",bufsize));
		return(IFFERR_NOMEM);
		}
	    error = loadbody2(iff, bitmap, NULL, bmhd, buffer, bufsize);
	    D(bug("Returned from getbody, error = %ld\n",error));
	    }
	FreeMem(buffer,bufsize);
	return(error);
	}


/* like the old GetBODY */
LONG loadbody2(iff, bitmap, mask, bmhd, buffer, bufsize)
struct IFFHandle *iff;
struct BitMap *bitmap;
BYTE *mask;
BitMapHeader *bmhd;
BYTE *buffer;
ULONG bufsize;
   {
   UBYTE srcPlaneCnt = bmhd->nPlanes;   /* Haven't counted for mask plane yet*/
   WORD srcRowBytes = RowBytes(bmhd->w);
   WORD destRowBytes = bitmap->BytesPerRow;
   LONG bufRowBytes = MaxPackedSize(srcRowBytes);
   int nRows = bmhd->h;
   WORD compression = bmhd->compression;
   register int iPlane, iRow, nEmpty;
   register WORD nFilled;
   BYTE *buf, *nullDest, *nullBuf, **pDest;
   BYTE *planes[MaxSrcPlanes]; /* array of ptrs to planes & mask */
   struct ContextNode *cn;

   D(bug("srcRowBytes = %ld\n",srcRowBytes));

   cn = CurrentChunk(iff);

   if (compression > cmpByteRun1)
      return(CLIENT_ERROR);

   D(bug("loadbody2: compression=%ld srcBytes=%ld bitmapBytes=%ld\n",
		compression, srcRowBytes, bitmap->BytesPerRow));
   D(bug("loadbody2: bufsize=%ld bufRowBytes=%ld, srcPlaneCnt=%ld\n",
			bufsize, bufRowBytes, srcPlaneCnt));

   /* Complain if client asked for a conversion GetBODY doesn't handle.*/
   if ( srcRowBytes  >  bitmap->BytesPerRow  ||
         bufsize < bufRowBytes * 2  ||
         srcPlaneCnt > MaxSrcPlanes )
      return(CLIENT_ERROR);

   D(bug("loadbody2: past conversion checks\n"));

   if (nRows > bitmap->Rows)   nRows = bitmap->Rows;

   D(bug("loadbody2: srcRowBytes=%ld, srcRows=%ld, srcDepth=%ld, destDepth=%ld\n",
		srcRowBytes, nRows, bmhd->nPlanes, bitmap->Depth));
   
   /* Initialize array "planes" with bitmap ptrs; NULL in empty slots.*/
   for (iPlane = 0; iPlane < bitmap->Depth; iPlane++)
      planes[iPlane] = (BYTE *)bitmap->Planes[iPlane];
   for ( ;  iPlane < MaxSrcPlanes;  iPlane++)
      planes[iPlane] = NULL;

   /* Copy any mask plane ptr into corresponding "planes" slot.*/
   if (bmhd->masking == mskHasMask)
	{
      	if (mask != NULL)
             planes[srcPlaneCnt] = mask;  /* If there are more srcPlanes than
               * dstPlanes, there will be NULL plane-pointers before this.*/
      	else
             planes[srcPlaneCnt] = NULL;  /* In case more dstPlanes than src.*/
      	srcPlaneCnt += 1;  /* Include mask plane in count.*/
      	}

   /* Setup a sink for dummy destination of rows from unwanted planes.*/
   nullDest = buffer;
   buffer  += srcRowBytes;
   bufsize -= srcRowBytes;

   /* Read the BODY contents into client's bitmap.
    * De-interleave planes and decompress rows.
    * MODIFIES: Last iteration modifies bufsize.*/

   buf = buffer + bufsize;  /* Buffer is currently empty.*/
   for (iRow = nRows; iRow > 0; iRow--)
	{
      	for (iPlane = 0; iPlane < srcPlaneCnt; iPlane++)
	    {
 	    pDest = &planes[iPlane];

            /* Establish a sink for any unwanted plane.*/
            if (*pDest == NULL)
		{
	    	nullBuf = nullDest;
            	pDest   = &nullBuf;
            	}

            /* Read in at least enough bytes to uncompress next row.*/
            nEmpty  = buf - buffer;	  /* size of empty part of buffer.*/
            nFilled = bufsize - nEmpty;	  /* this part has data.*/
	    if (nFilled < bufRowBytes)
		{
	    	/* Need to read more.*/

	    	/* Move the existing data to the front of the buffer.*/
	    	/* Now covers range buffer[0]..buffer[nFilled-1].*/
            	movmem(buf, buffer, nFilled);  /* Could be moving 0 bytes.*/

            	if(nEmpty > ChunkMoreBytes(cn))
		    {
               	    /* There aren't enough bytes left to fill the buffer.*/
               	    nEmpty = ChunkMoreBytes(cn);
               	    bufsize = nFilled + nEmpty;  /* heh-heh */
               	    }

	    	/* Append new data to the existing data.*/
            	if(ReadChunkBytes(iff, &buffer[nFilled], nEmpty) < nEmpty)
			return(CLIENT_ERROR);

            	buf     = buffer;
	    	nFilled = bufsize;
	    	nEmpty  = 0;
	    	}

 	    /* Copy uncompressed row to destination plane.*/
            if(compression == cmpNone)
		{
            	if(nFilled < srcRowBytes)  return(IFFERR_MANGLED);
	    	movmem(buf, *pDest, srcRowBytes);
	    	buf    += srcRowBytes;
            	*pDest += destRowBytes;
            	}
	    else
		{
         	/* Decompress row to destination plane.*/
            	if ( unpackrow(&buf, pDest, nFilled,  srcRowBytes) )
                    /*  pSource, pDest, srcBytes, dstBytes  */
               		return(IFFERR_MANGLED);
	    	else *pDest += (destRowBytes - srcRowBytes);
		}
	    }
	}
   return(IFF_OKAY);
   }


/* ----------- getcolors ------------- */

/* getcolors - allocates a ilbm->colortable for at least MAXAMCOLORREG
 *      and loads CMAP colors into it, setting ilbm->ncolors to number
 *      of colors actually loaded.
 */ 
LONG getcolors(struct ILBMInfo *ilbm)
	{
	struct IFFHandle	*iff;
	int error = 1;

	if(!(iff=ilbm->ParseInfo.iff))	return(CLIENT_ERROR);

	if(!(error = alloccolortable(ilbm)))
	   error = loadcmap(iff, ilbm->colortable, &ilbm->ncolors);
	if(error) freecolors(ilbm);
	D(bug("getcolors: error = %ld\n",error));
	return(error);
	}


/* alloccolortable - allocates ilbm->colortable and sets ilbm->ncolors
 *	to the number of colors we have room for in the table.
 */ 

LONG alloccolortable(struct ILBMInfo *ilbm)
	{
	struct IFFHandle	*iff;
	struct	StoredProperty	*sp;

	LONG	error = CLIENT_ERROR;
	ULONG	ctabsize;
	USHORT	ncolors;

	if(!(iff=ilbm->ParseInfo.iff))	return(CLIENT_ERROR);

	if(sp = FindProp (iff, ID_ILBM, ID_CMAP))
		{
		/*
		 * Compute the size table we need
		 */
		ncolors = sp->sp_Size / 3;		/* how many in CMAP */
		ncolors = MAX(ncolors, MAXAMCOLORREG);

		ctabsize = ncolors * sizeof(Color4);
		if(ilbm->colortable = 
		   (Color4 *)AllocMem(ctabsize,MEMF_CLEAR|MEMF_PUBLIC))
		    {
		    ilbm->ncolors = ncolors;
		    ilbm->ctabsize = ctabsize;
		    error = 0L;
		    }
		else error = IFFERR_NOMEM;
		}
	D(bug("alloccolortable for %ld colors: error = %ld\n",ncolors,error));
	return(error);
	}


void freecolors(struct ILBMInfo *ilbm)
	{
	if(ilbm->colortable)
		{
		FreeMem(ilbm->colortable, ilbm->ctabsize);
		}
	ilbm->colortable = NULL;
	ilbm->ctabsize = 0;
	}



/* Passed IFFHandle, pointer to colortable array, and pointer to
 * a USHORT containing number of colors caller has space to hold,
 * loads the colors and sets pNcolors to the number actually read.
 *
 * NOTE !!! - Old GetCMAP passed a pointer to a UBYTE for pNcolors
 *            This one is passed a pointer to a USHORT
 */
LONG loadcmap(struct IFFHandle *iff, WORD *colortable,USHORT *pNcolors)
	{
	register struct StoredProperty	*sp;
	register LONG			idx;
	register ULONG			ncolors;
	register UBYTE			*rgb;
	LONG				r, g, b;

	if(!(colortable))
		{
		message(SI(MSG_IFFP_NOCOLORS));
		return(1);
		}

	if(!(sp = FindProp (iff, ID_ILBM, ID_CMAP)))	return(1);

	rgb = sp->sp_Data;
	ncolors = sp->sp_Size / sizeofColorRegister;
	if(*pNcolors < ncolors)	ncolors = *pNcolors;
	*pNcolors = ncolors;

	idx = 0;
        while (ncolors--) 
                {
                r = (*rgb++ & 0xF0) << 4;
                g = *rgb++ & 0xF0;
                b = *rgb++ >> 4;
                colortable[idx] = r | g | b;
                idx++;
                }
        return(0);
        }

/*
 * Returns CAMG or computed mode for storage in ilbm->camg
 *
 * ilbm->Bmhd structure must be initialized prior to this call.
 */
ULONG getcamg(struct ILBMInfo *ilbm)
	{
	struct IFFHandle *iff;
	struct StoredProperty *sp;
	UWORD  wide,high,deep;
	ULONG modeid = 0L;

    	if(!(iff=ilbm->ParseInfo.iff))	return(0L);

	wide = ilbm->Bmhd.pageWidth;
	high = ilbm->Bmhd.pageHeight;
	deep = ilbm->Bmhd.nPlanes;

	D(bug("Getting CAMG for w=%ld h=%ld d=%ld ILBM\n",wide,high,deep));

        /*
         * Grab CAMG's idea of the viewmodes.
         */
        if (sp = FindProp (iff, ID_ILBM, ID_CAMG))
                {
                modeid = (* (ULONG *) sp->sp_Data);

                /* knock bad bits out of old-style 16-bit viewmode CAMGs
                 */
                if((!(modeid & MONITOR_ID_MASK))||
		  ((modeid & EXTENDED_MODE)&&(!(modeid & 0xFFFF0000))))
                   modeid &= 
		    (~(EXTENDED_MODE|SPRITES|GENLOCK_AUDIO|GENLOCK_VIDEO|VP_HIDE));

                /* check for bogus CAMG like DPaintII brushes
                 * with junk in upper word and extended bit
                 * not set in lower word.
                 */
                if((modeid & 0xFFFF0000)&&(!(modeid & 0x00001000))) sp=NULL;
                }

        if(!sp) {
                /*
                 * No CAMG (or bad CAMG) present; use computed modes.
                 */
                if (wide >= 640)        modeid = HIRES;
                if (high >= 400)        modeid |= LACE;
                if (deep == 6)
                        {
                        modeid |= ilbm->EHB ? EXTRA_HALFBRITE : HAM;
                        }
		D(bug("No CAMG found - using mode $%08lx\n",modeid));
                }

	D(bug("getcamg: modeid = $%08lx\n",modeid));
	return(modeid);
	}
