/*
 *  FILE: picture.c
 *  Support routines for reading IFF/ILBM files.
 *
 *  Public Domain, but keep our names in it as the original authors.
 *  08-Dec-87	Rico Mariani	    original code (modified by JST)
 *  31-Aug-88	Jan Sven Trabandt   first release version
 */


#define I_AM_PICTURE
#include "gimmelib/gimmefuncs.h"
#include "gimmelib/picture.h"
#include "gimmelib/postext.h"
#include "gimmelib/minterm.h"
#include <stdio.h>


#define GET(iffile)         getc( iffile )

#define getId		    getLong
#define getLen		    getLong
#define getByte 	    GET
#define skip(len, file)     fseek(file, (long)(len), 1)

#define getBMH(bmh, file)   fread(bmh, sizeof(BitMapHeader), 1, file)

/* requires an existent variable 'char *errmsg' and a label 'failure_return' */
#define fail_and_return(msg)    {errmsg = msg; goto failure_return;}

/* forward declarations */
static LONG getLong();
static WORD getWord();
static ULONG makeBM();

static char noMemMsg[] = "insufficient memory\n";
static char badFileMsg[] = "corrupt or incomplete file\n";


PICTURE *gimmePicture( filename, myflags )
    UBYTE   *filename;
    ULONG   myflags;
{
    LONG    len, id;
    PICTURE *pic = NULL;
    BYTE    *p;
    void    *mh = NULL;
    FILE    *file = NULL;
    char    *errmsg = " ";
    SHORT   i;

    if( !(file = fopen(filename, "r")) ) {
	fail_and_return( "unable to open iff picture file for read\n" );
    }
    if( getId(file) != ID_FORM ) {
	fail_and_return( "not IFF format\n" );
    }
    getLen( file );
    if( getId(file) != ID_ILBM ) {
	fail_and_return( "not ILBM format\n" );
    }
    for( ;; ) {
	getChunk( &id, &len, file );
	if( ferror(file) || feof(file) ) {
	    fail_and_return( badFileMsg );
	}
	switch( id ) {
	  case ID_BMHD:
	    pic = chainAllocMem( &mh, (ULONG)sizeof(PICTURE),
				    MEMF_PUBLIC | MEMF_CLEAR );
	    if( !pic ) {
	       fail_and_return( noMemMsg );
	    }
	    getBMH( &pic->bmh, file );
	    if( len & 1 ) {
		GET(file);
	    }
	    break;
	  case ID_CMAP:
	    if( getLut(pic, len, file) ) {
		fail_and_return( "unable to get color table\n" );
	    }
	    break;
	  case ID_BODY:
	    if( !pic ) {
		fail_and_return( "didn't find BMHD chunk\n" );
		return( NULL );
	    }
	    switch( pic->bmh.masking ) {
	      case mskNone:
	      case mskLasso:
	      case mskHasTranCol:
		break;
	      default:
		printf("Error, unsupported masking type\n");
		return( NULL );
	    } /* switch */

	    pic->xsize = pic->bmh.w;
	    if( (pic->myflags = makeBM(pic, myflags)) == GBP_ERROR ) {
		return( NULL );
	    }
	    switch( pic->bmh.compression ) {
	      case cmpNone:
		if( getBody(pic->bm, pic->myflags, file) ) {
		    fail_and_return( "error in uncompressed body\n" );
		}
		break;
	      case cmpByteRun1:
		if( getCmpBody(pic->bm, file) ) {
		    fail_and_return( "error in compressed body\n" );
		}
		break;
	      default:
		fail_and_return( "unknown compression form\n" );
		return( NULL );
	    } /* switch */
	    if( ferror(file) ) {
		fail_and_return( badFileMsg );
	    }
	    pic->memhead = mh;
	    fclose( file );
	    return( pic );
	    break;
	  case ID_DEST:
	  case ID_SPRT:
	  case ID_CAMG:
	  case ID_CRNG:
	  case ID_CCRT:
	  default:		/* unimplemented chunk type */
	    skip( EVEN(len), file );
	    break;
	} /* switch */
    } /* for */
failure_return: {}
    if( Output() ) {
	Write( Output(), errmsg, (ULONG)strlen(errmsg) );
    }
    if( mh ) {
	chainFreeMem( mh );
    }
    if( file ) {
	fclose( file );
    }
    return( NULL );
} /* gimmePicture */


static ULONG makeBM( pic, myflags )
    register PICTURE	*pic;
    ULONG		myflags;
{
    UBYTE *plane;

    if( !(pic->bm = AllocMem((ULONG)sizeof(struct BitMap), MEMF_PUBLIC)) ) {
	return( GBP_ERROR );
    }
    InitBitMap( pic->bm, (long) pic->bmh.nPlanes, (long) pic->bmh.w,
		    (long) pic->bmh.h );
    return( gimmeBitPlanes(pic->bm, myflags) );
} /* makeBM */


short getRidOfPicture( picture )
    PICTURE   *picture;
{
#ifdef GIMME_WIMPY
    if( picture ) {
	return( -1 );
    }
#endif
    if( picture->colormap.ColorTable ) {
	FreeMem( picture->colormap.ColorTable,
		    (ULONG) picture->colormap.Count * sizeof(USHORT) );
    }
    if( picture->bm ) {
	getRidOfBitPlanes( picture->bm, picture->myflags );
	FreeMem( picture->bm, (ULONG)sizeof(struct BitMap) );
    }
    chainFreeMem( picture->memhead );
    return( 0 );
} /* getRidOfPicture */


short positionPicture( rp, myflags, pic, minterm, x, y )
    struct RastPort *rp;
    ULONG	    myflags;
    PICTURE	    *pic;
    SHORT	    minterm;
    register SHORT  x, y;
{
    SHORT	    left, top;
    SHORT	    width, height;
    SHORT	    temp;
    struct BitMap   *bm;

#ifdef GIMME_WIMPY
    if( !pic || !rp ) {
	return( -1 );
    }
#endif
    left = top = 0;
    width = pic->bmh.w;
    height = pic->bmh.h;
    bm = rp->BitMap;
    switch( myflags & GPT_XFLAGS ) {
      case GPT_XCENTRE:
	x -= width >> 1;
	break;
      case GPT_XRIGHT:
	x -= width;
	break;
      case GPT_XLEFT:
      default:
	break;
    } /* switch */
    switch( myflags & GPT_YFLAGS ) {
      case GPT_YBOTTOM:
      case GPT_YBASELINE:
	y -= height;
	break;
      case GPT_YCENTRE:
      case GPT_YCENTREBASE:
	y -= height >> 1;
	break;
      case GPT_YTOP:
      default:
	break;
    } /* switch */
    if( minterm != GIM_MINTERM_DEST ) {
	if( x < 0 ) {
	    width += x;
	    left -= x;
	    x = 0;
	} else if( (temp = x + width - (bm->BytesPerRow << 3)) > 0 ) {
	    width -= temp;
	}
	if( y < 0 ) {
	    height += y;
	    top -= y;
	    y = 0;
	} else if( (temp = y + height - bm->Rows) > 0 ) {
	    height -= temp;
	}
	if( width > 0 && height > 0 ) {
	    BltBitMapRastPort( pic->bm, (long) left, (long) top, rp,
				(long) x, (long) y,
				(long) width, (long) height, (long) minterm );
	}
    }
    return( 0 );
} /* positionPicture */


short usePictureColors( pic, screen )
    register PICTURE *pic;
    struct Screen    *screen;
{
#ifdef GIMME_WIMPY
    if( !pic || !screen ) {
	return( -1 );
    }
#endif
    return( setColors(screen, pic->colormap.ColorTable,
			(SHORT) pic->colormap.Count) );
} /* usePictureColors */


/* read an IFF Id */
static LONG getLong( file )
    FILE    *file;
{
    register UBYTE  p[4];

    p[0] = GET(file);
    p[1] = GET(file);
    p[2] = GET(file);
    p[3] = GET(file);
    return( (LONG) Mk(p[0],p[1],p[2],p[3]) );
} /* getLong */


/* read an IFF chunk header */
static getChunk( id, len, file )
    LONG    *id, *len;
    FILE    *file;
{
    *id = getId( file );
    *len = getLen( file );
} /* getChunk */


/* get len bytes from the file and put them into memory */
static memget( p, len, file )
    UBYTE   *p;
    LONG    len;
    FILE    *file;
{
    for( ; len > 0L; len -= 0x07fffL, p += 0x07fffL ) {
	fread( p, (int) (len & 0x07fffL), 1, file );
    } /* for */
} /* memget */


/* get a word */
static WORD getWord( file )
    FILE    *file;
{
    WORD    v;

    v = GET(file) << 8;
    return( v | GET(file) );
} /* getWord */


static int getCmpBody( bm, file )
    struct BitMap   *bm;
    FILE	    *file;
{
    register short  x;
    short	    y, pl;
    UBYTE	    *plane;
    UBYTE	    run_len, verb_len, run_byte;
    BYTE	    v;
    register BYTE   mybyte;

    verb_len = run_len = 0;
    /* note: the bit-planes should be clear already */
    for( y = 0; y < bm->Rows; ++y ) {
	for( pl = 0; pl < bm->Depth; ++pl ) {
	    plane = bm->Planes[pl] + y * bm->BytesPerRow;
	    for( x = 0; x < bm->BytesPerRow; ++x ) {
		if( run_len ) {
		    --run_len;
		    mybyte = run_byte;
		} else if( verb_len ) {
		    --verb_len;
		    mybyte = GET(file);
		} else if( (v = GET(file)) >= 0 ) {
		    verb_len = v;
		    mybyte = GET(file);
		} else {
		    run_len = -v;
		    mybyte = run_byte = GET(file);
		}
		plane[x] = mybyte;
	    } /* for */
	} /* for */
    } /* for */
    return( 0 );
} /* getCmpBody */


static int getBody( bm, flags, file )
    register struct BitMap *bm;
    ULONG   flags;
    FILE    *file;
{
    register short  i;

    if( flags & GBP_CONTIGUOUS ) {
	memget( bm->Planes[0], (ULONG)(bm->Depth) * bm->Rows * bm->BytesPerRow,
		    file );
    } else {
	for( i = 0; i < bm->Depth; ++i ) {
	    memget( bm->Planes[i], (ULONG)(bm->Rows) * bm->BytesPerRow, file );
	} /* for */
    }
    return( 0 );
} /* getBody */


static int getLut( pic, len, file )
    PICTURE *pic;
    LONG    len;
    FILE    *file;
{
    SHORT   i, ncols;
    USHORT  red, green, blue;
    USHORT   *lut;

    pic->colormap.Flags = pic->colormap.Type = 0;
    ncols = len / 3;
    lut = (USHORT *) AllocMem( (ULONG) ncols * sizeof(USHORT), MEMF_PUBLIC );
    if( !lut ) {
	return( -1 );
    }
    for( i = 0; i < ncols; ++i ) {
	red = GET(file) >> 4;
	green = GET(file) >> 4;
	blue = GET(file) >> 4;
	lut[i] = (red << 8) | (green << 4) | blue;
    } /* for */
    if( len & 1 ) {
	GET(file);
    }
    pic->colormap.Count = ncols;
    pic->colormap.ColorTable = (APTR) lut;
    return( 0 );
} /* getLut */
