/*
 * xviff.c  -  IFF image file i/o routines
 */

/*
 *  ILBM's can be compressed or uncompressed, only uncompressed
 *  By Brett Van Sprewenburg (bpv9073@park.kodak.com or bpv9073@sjfc.edu)
 * 
 * Credit to the ppm utilities, the developer(s) of xv, and Commodore-Amiga for
 * code and ideas.
 *
 *  Version	Date	
 *   1.0	12/??/92	Initial attempts to read the 24 bit IFF 
 *                                 ILBM format
 *   1.1	2/4/93		Cleaned code, fixed bugs with read and 
 *                                 writeilbm intergration
 *   1.2	2/9/93		ANSI'fied code some more for compilation 
 *                                 with acc
 *   1.2.1	2/10/93		Checks to make sure it's a valid iff file, 
 *                                 looks for FORM chuck id
 *				Fixed a couple of minor printf bugs. 
 *   1.2.2	2/11/93		Checks to make sure that there are 24 bit 
 *                                 planes in the IFF.
 *				XVified the whole thing
 */

/* additional mods to hook it into XV 3.00 by John Bradley, 2/15/93 */


#include "xv.h"

#define RowBytes(cols)		( ( ( (int)(cols) + 15 ) / 16 ) * 2 )

/* definitions for BMHD */

struct BitMapHeader {
    unsigned short w, h;
    short x, y;
    unsigned char nPlanes, masking, compression, pad1;
    unsigned short transparentColor;
    unsigned char xAspect, yAspect;
    short pageWidth, pageHeight;
  };

#define mskNone			0
#define mskHasMask		1
#define mskHasTransparentColor	2
#define mskLasso		3

#define cmpNone			0
#define cmpByteRun1		1

typedef struct { unsigned char r, g, b; } pixel;

#define ASSIGN(p,red,grn,blu) { (p).r = (red); (p).g = (grn); (p).b = (blu); }
#define ALLOCROW( cols ) ((pixel*) malloc( cols * sizeof(pixel) ))

pixel *pixelrow;

struct BitMapHeader ilbmheader;

#define ILBMHSIZE 40	/* Size of written ilbm header in bytes */


/*----------------------------------------------------------------------*
 * PACKER.H  typedefs for Data-Compresser.  		        1/22/86
 *
 * This module implements the run compression algorithm "cmpByteRun1"; the
 * same encoding generated by Mac's PackBits.
 *
 * By Jerry Morrison and Steve Shaw, Electronic Arts.
 * This software is in the public domain.
 *
 * This version for the Commodore-Amiga computer.
 * 
 * This particular version has been slightly modified for this
 * distribution by Brett Van Sprewenburg, Eastman Kodak
 * <bpv9073@park.kodak.com or bpv9073@sjfc.edu>
 *----------------------------------------------------------------------*/

/* This macro computes the worst case packed size of a "row" of bytes. */
#define MaxPackedSize(rowSize)  ( (rowSize) + ( ((rowSize)+127) >> 7 ) )




static int iffError();
static void iffWarning();
static int  getint32();
static int  getint16();
static void putint32();
static void putint16();

char *bname;
long filesize;

/******************************************/
int LoadIFF(fname, pinfo)
     char *fname;
     PICINFO *pinfo;
/******************************************/
{
  /* returns '1' if successful, '0' if not */

  FILE	*fp;
  char	form[5];
  byte	*ubp, *bp, *runbuf;
  byte	*Rrow, *Brow, *Grow;
  byte 	*body = 0;	/* To hold crazy IFF body data */
  byte	*image = 0;
  unsigned int mybyte;
  int 	totbytes,bytes,row,col,plane,j,w,h,d;
  long	bmsize,formsize;
  unsigned long body_chunk_size;

  pinfo->pic = (byte *) NULL;
  pinfo->comment = (char *) NULL;
  bname = BaseName(fname);

  fp = fopen(fname,"r");
  if (!fp) return ( iffError(pinfo, "can't open file"));

  /* find the size of the file */
  fseek(fp, 0L, 2);
  filesize = ftell(fp);
  fseek(fp, 0L, 0);
  
  fread(form,4,1,fp);		      /* FORM */
  if (strcmp(form,"FORM") != NULL) {
    return ( iffError(pinfo, "not an IFF file."));   /* shouldn't happen. */
  }

  formsize = getint32(fp);      /* size of whole form - 8 */
  fread(form,4,1,fp);		/* ILBM */
  fread(form,4,1,fp);		/* BMHD */
  bmsize = getint32(fp);	/* size of BMHD */

  /* load up header info */
  ilbmheader.w = getint16(fp);
  ilbmheader.h = getint16(fp);
  ilbmheader.x = getint16(fp);
  ilbmheader.y = getint16(fp);

  ilbmheader.nPlanes = getc(fp);
  ilbmheader.masking = getc(fp);
  ilbmheader.compression = getc(fp);
  getc(fp);  /* pad1 */

  ilbmheader.transparentColor = getint16(fp);
  ilbmheader.xAspect = getc(fp);
  ilbmheader.yAspect = getc(fp);
  ilbmheader.pageWidth = getint16(fp);
  ilbmheader.pageHeight = getint16(fp);

  if (ferror(fp)) {
    fclose(fp);
    return (iffError("error reading IFF header."));
  }


  w = ilbmheader.w;
  h = ilbmheader.h;
  d = ilbmheader.nPlanes;


  if (d != 24) {
    fclose(fp);
    return (iffError("only 24-bit IFF files are supported."));
  }
 
  fread(form,4,1,fp);		/* BODY (ANNO or CMAP sometimes) */
  
  while ((strcmp(form,"BODY") != 0)) 	/* Find the damn thing */
    fread(form,4,1,fp);
        
  /* Read in the size of the body chunk */
  body_chunk_size = getint32(fp);

  pixelrow = ALLOCROW(w);
  image    = (byte *) malloc(w * h * 3);
  body     = (byte *) malloc(body_chunk_size);
  runbuf   = (byte *) malloc(RowBytes(w));
  Rrow     = (byte *) malloc(w);
  Grow     = (byte *) malloc(w);
  Brow     = (byte *) malloc(w);

  if (!pixelrow || !image || !body || !runbuf || !Rrow || !Grow || !Brow)
    FatalError("can't malloc() in LoadIFF()");

  bp = body;

  /* Read in the body of the ilbm */
  fread(body, body_chunk_size, 1, fp); 
  if (ferror(fp)) iffWarning("IFF file appears to be truncated.");
  fclose(fp);


  for (row=0; row<h; row++) {

    for (col=0; col<w; col++)
      Rrow[col] = Grow[col] = Brow[col] = 0;

    /* Image is compressed by plane, line by line */
    for (plane=0; plane<d; plane++) {
      switch (ilbmheader.compression) {
      case 0:		/* Uncompressed IFF */
	ubp = bp;
	bp += RowBytes(w);
	break;

      case 1:		/* RLE Compressed IFF */
	ubp = runbuf;
	totbytes = RowBytes(w);
	do {
	  mybyte = *bp++;
	  if ( mybyte <= 127 )
	    for ( j = mybyte, totbytes -= j + 1; j >= 0; j--) {
	      *ubp++ = *bp++;
	    }
	  else if ( mybyte != 128 )
	    for (j = 256 - mybyte, totbytes -= j + 1, mybyte = *bp++; 
		 j >= 0; j--) {
	      *ubp++ = mybyte;
	    }
	} while ( totbytes > 0 );

	ubp = runbuf;
	break;
      }

      /* Break raw row buffer up into separate rgb values */
      /* In order to understand this a little better, it might help */
      /* to know that the pixel values in an ILBM appear to be stored */
      /* bit reversed. Hence all the logical bitwise juju. */

      for (col=0; col<w; col++)
	if (plane < 8) { /* red */
	  if (ubp[col / 8] & ( 128 >> ( col % 8 ) ) )
	    Rrow[col] |= 1 << plane;
	}
	else if ( plane > 15 ) { /* blue */
	  if ( ubp[col / 8] & ( 128 >> ( col % 8 ) ) )
	    Brow[col] |= 1 << (plane-16);
	}
	else { /* green */
	  if ( ubp[col / 8] & ( 128 >> ( col % 8 ) ) )
	    Grow[col] |= 1 << (plane-8);
	}
    }

    /* Get the rgb values out of pixel structure, pointed at by pixelrow */
    for ( col = 0; col < w ; col++) 
      ASSIGN(pixelrow[col],Rrow[col],Grow[col],Brow[col]);
  
    /* Put the pixel data in a 24 bit image space */
    memcpy(image + w * row * 3, pixelrow, w * 3); 
  }


  pinfo->pic     = image;
  pinfo->w       = w;  
  pinfo->h       = h;
  pinfo->type    = PIC24;
  pinfo->frmType = F_ILBM;
  pinfo->colType = F_FULLCOLOR;

  sprintf(pinfo->fullInfo, 
	  "Amiga %s IFF24 ILBM. (%d planes)  (%ld bytes)",
	  (ilbmheader.compression == cmpByteRun1) ? 
	          "compressed" : "uncompressed",
	  d, filesize);

  sprintf(pinfo->shrtInfo, "%dx%d Amiga ILBM.", w, h);


  /* clean up */
  free(pixelrow);
  free(body);
  free(runbuf);
  free(Rrow);		/* Fly! Be Free!! */
  free(Grow);
  free(Brow);

  return 1;
}


/*
 *  Writeilbm.c
 *
 *  Writes out a 24 bit IFF ILBM file, compressed or uncompressed.
 *  By Brett Van Sprewenburg
 * 
 *  This is a fairly complex format, or at least I thought so... :-)
 *
 * Version
 *   1.0 	2/1/93	First working version, with hardcoded form and 
 *                        body chunk sizes.
 *			Only uncompressed images work.
 *   1.1	2/2/93	Added in the correct form and body sizes with 
 *                      compression by altering
 *			the file after it was written.
 *
 *   1.2	2/4/93	First working version with compression done correctly.
 *                      I still wouldn't be able to tell you exactly what 
 *                      was wrong. Fixed multiple things with readilbm 
 *                      integration. The biggest problem with this version 
 *                      is that it's sloooow.
 *  			A faster way may be to do 8 planes at once, instead of
 *                      a plane at a time.
 *   1.3	2/9/93  Fixed previous slowness by compiling with acc -fast. 
 *                      ANSI'fied code also for 
 *			compilation with acc without warnings.
 *   1.4	2/10/93 Added reading and conversion of 8 bit rasters 
 *                      into 24 bit ilbms.
 *
 */


/******************************************************************/
int WriteILBM(fp, pic, ptype, w, h, rmap, gmap, bmap, numcols, colorstyle, 
	      compression)
     FILE *fp;
     byte *pic;
     int   ptype, w, h;
     byte *rmap, *gmap, *bmap;
     int   numcols, colorstyle, compression;
/******************************************************************/
{
  byte *tpic, *pp, *tp;
  int formsize, bmhdsize, bodysize;
  byte *coded_rowbuf,*cp,*raw_rowbuf;
  int rel_count, rgbline, linesize, mask, yoffset, offset, push;
  int row, col, plane;
  unsigned int bit;
  int pad,tot_bytes,packedbytes,ycoffset,coffset,ccol = 0;

  linesize = RowBytes(w);
  bmhdsize = 4 + 4 + 4 + 2 + 2 + 4;   /* size of header info written */

  if (!compression) {
    bodysize = h * linesize * 24;
    formsize = 4 + 4 + 4 + bmhdsize + 4 + 4 + bodysize;
  }
  else {
    bodysize = 999999;		
    formsize = 999999;  /* don't know how much compression we'll get */
  }


  /* write out header info */
  fwrite("FORM", 4, 1, fp);
  putint32(formsize, fp);
  fwrite("ILBM", 4, 1, fp);
  fwrite("BMHD", 4, 1, fp);
  putint32(bmhdsize, fp);
  putint16(w, fp);
  putint16(h, fp);
  putint16(0, fp);   /* x */
  putint16(0, fp);   /* y */
  putc(24, fp);      /* nPlanes */
  putc(mskNone, fp); /* masking */
  putc(compression ? cmpByteRun1 : cmpNone, fp);
  putc(0, fp);       /* pad1 */
  putint16(0, fp);   /* transparent color */
  putc(10, fp);      /* xAspect */
  putc(10, fp);      /* yAspect */
  putint16(w, fp);   /* pageWidth */
  putint16(h, fp);   /* page height */

  fwrite("BODY",4,1,fp);
  putint32(bodysize,fp);

  /* write the image data */

  /* if pic is anything other than F_FULLCOLOR, PIC24, build a pic24 to hold
     an appropriate version of the pic */
  if (ptype == PIC24 && colorstyle == F_FULLCOLOR) tpic = pic;
  else {
    tpic = (byte *) malloc(w * h * 3);
    if (!tpic) FatalError("can't alloc memory in WriteIFF()");

    if (ptype == PIC8) {
      for (i=0, pp=pic, tp=tpic; i<w*h; i++,pp++) {
	if (colorstyle == F_GREYSCALE) {
	  j = MONO(rmap[*pp],gmap[*pp],bmap[*pp]);
	  *tpic++ = j;  *tpic++ = j;  *tpic++ = j;
	}
	else {
	  *tpic++ = rmap[*pp];
	  *tpic++ = gmap[*pp];
	  *tpic++ = bmap[*pp];
	}
      }
    }
    else {  /* if PIC24 */
      if (colorstyle == F_GREYSCALE) {
	for (i=0, tp=tpic; i<w*h; i++) {
	  tp[0] = MONO(tp[0],tp[1],tp[2]);
	  tp[1] = tp[2] = tp[0];
	}
      }
      else {
	free(tpic);
	tpic = pic;
      }
    }
  }

  /* at this point, tpic points to an appropriate 24-bit image to dump */
    
  tot_bytes = packedbytes = 0;

  rgbline = w * 3; 
  coded_rowbuf = (byte *) malloc(w);
  raw_rowbuf   = (byte *) malloc(rgbline);

  if (!coded_rowbuf || !raw_rowbuf)
    FatalError("out of memory in WriteIFF()");


  /* This next whole thing took quite a while to figure out 
     This file format looks like this:  ( all data is bit reversed )
     Plane	   data		     scanline
     -------------------------------------------
     Plane 0:	RowBytes of red		0
     Plane 1:	RowBytes of red		0
     |
     Plane 7:	RowBytes of red		0
     Plane 8:	RowBytes of green	0
     |
     Plane 15:	RowBytes of green	0
     Plane 16:	RowBytes of blue	0
     |
     Plane 23:	RowBytes of blue	0
     Plane 0:	RowBytes of red		1
     Plane 1:	RowBytes of red		1
     and the beat goes on...
     */

  /* Figure this next bit out for yourself :-<  */

  for (row=0; row<h; row++) {
    yoffset = row * rgbline;
    for (plane=0; plane<24; plane++) {
      if      (plane <  8) push = plane;
      else if (plane > 15) push = plane - 16;
      else                 push = plane - 8;

      mask = 1 << push;		/* how many bits to push */

      cp = coded_rowbuf;
      *cp = 0;

      for (col=(plane / 8), rel_count=0;  col<rgbline;  rel_count++, col+=3) {
	offset = yoffset + col;
	bit = (*(tpic + offset) & mask) ? 1 : 0;
	*cp |= bit << ( 7 - (rel_count % 8) );
	if ((rel_count % 8) == 7) {	/* Every 8th byte, advance pixel */
	  cp++;  *cp = 0;
        }
      }

      if (compression) {	/* This bit is pretty obvious. */
	packedbytes = packrow(coded_rowbuf, raw_rowbuf, linesize);
	tot_bytes += packedbytes;
	fwrite(raw_rowbuf, packedbytes, 1, fp);
      }
      else			/* Write uncompressed data */
        fwrite(coded_rowbuf, linesize, 1, fp);
    }
  }


  /* if we compressed, have to go fix bogus values we wrote earlier */

  if (compression) {
    formsize = tot_bytes + 4 + 4 + 4 + bmhdsize + 4 + 4;
    fseek(fp,4L,0);
    putint32(formsize, fp);
    fseek(fp,44L,0);
    putint32(tot_bytes, fp);
  }

  if ((tot_bytes % 2) != 0) { /* Pad the body chunk out to an even byte */
    fseek(fp,0L,2);
    pad = 0;
    fwrite(&pad,1,1,fp); 
  }

  free(coded_rowbuf);
  free(raw_rowbuf);

  if (tpic != pic) free(tpic);

  return 0;
}







/*----------------------------------------------------------------------*
 * packer.c Convert data to "cmpByteRun1" run compression.     11/15/85
 *
 * By Jerry Morrison and Steve Shaw, Electronic Arts.
 * This software is in the public domain.
 *
 *	control bytes:
 *	 [0..127]   : followed by n+1 bytes of data.
 *	 [-1..-127] : followed by byte to be repeated (-n)+1 times.
 *	 -128       : NOOP.
 *
 * This version for the Commodore-Amiga computer.
 * 
 * Slightly modified by Brett Van Sprewenburg, Eastman Kodak.
 * <bpv9073@park.kodak.com>

 * It will now accept only a pointer to the image data, not pointers to pointers.
 *
 *----------------------------------------------------------------------*/

#include "xv.h"
#include "packer.h"

#define DUMP	0
#define RUN	1

#define MinRun 3	
#define MaxRun 128
#define MaxDat 128

/* When used on global definitions, static means private.
 * This keeps these names, which are only referenced in this
 * module, from conficting with same-named objects in your program.
 */ 
static int putSize;
static byte buf[128];	/* [TBD] should be 128?  on stack?*/

#define GetByte()	(*source++)
#define PutByte(c)	{ *dest++ = (c);   ++putSize; }


byte *PutDump(dest, nn)
byte *dest;
int nn;
	{
	int i;

	PutByte(nn-1);
	for(i = 0;  i < nn;  i++)   PutByte(buf[i]);
	return(dest);
	}

byte *PutRun(dest, nn, cc)
byte *dest;
int nn, cc;
	{
	PutByte(-(nn-1));
	PutByte(cc);
	return(dest);
	}

#define OutDump(nn)   dest = PutDump(dest, nn)
#define OutRun(nn,cc) dest = PutRun(dest, nn, cc)

/*----------- packrow --------------------------------------------------*/
long packrow(pSource, pDest, rowSize)
byte *pSource;
byte *pDest;
long rowSize;
/*----------------------------------------------------------------------*/
    {
    byte *source, *dest;
    byte c;
    char lastc = '\0';
    int mode = DUMP;
    int nbuf = 0;		/* number of chars in buffer */
    int rstart = 0;		/* buffer index current run starts */

    source = pSource;
    dest = pDest;
    putSize = 0;
    buf[0] = lastc = c = GetByte();  /* so have valid lastc */
    nbuf = 1;   rowSize--;	/* since one byte eaten.*/


    for (;  rowSize;  --rowSize) {
	buf[nbuf++] = c = GetByte();
	switch (mode) {
		case DUMP: 
			/* If the buffer is full, write the length byte,
			   then the data */
			if (nbuf>MaxDat) {
				OutDump(nbuf-1);  
				buf[0] = c; 
				nbuf = 1;   rstart = 0; 
				break;
				}

			if (c == lastc) {
			    if (nbuf-rstart >= MinRun) {
				if (rstart > 0) OutDump(rstart);
				mode = RUN;
				}
			    else if (rstart == 0)
				mode = RUN;	/* no dump in progress,
				so can't lose by making these 2 a run.*/
			    }
			else  rstart = nbuf-1;		/* first of run */ 
			break;

		case RUN: if ( (c != lastc)|| ( nbuf-rstart > MaxRun)) {
	    		/* output run */
	   		OutRun(nbuf-1-rstart,lastc);
	    		buf[0] = c;
	    		nbuf = 1; rstart = 0;
	    		mode = DUMP;
	    		}
			break;
		}

	lastc = c;
	}

    switch (mode) {
	case DUMP: OutDump(nbuf); break;
	case RUN: OutRun(nbuf-rstart,lastc); break;
	}
    pSource = source;
    pDest = dest;
    return(putSize);
    }



/*****************************/
static int iffError(pinfo, st)
     PICINFO *pinfo;
     char    *st;
{
  iffWarning(st);

  if (pinfo->pic) free(pinfo->pic);
  if (pinfo->comment) free(pinfo->comment);

  pinfo->pic = (byte *) NULL;
  pinfo->comment = (char *) NULL;

  return 0;
}


/*****************************/
static void iffWarning(st)
     char *st;
{
  SetISTR(ISTR_WARNING,"%s:  %s", bname, st);
}


static int getint32(fp)
     FILE *fp;
{
  int i;

  i  = (getc(fp) & 0xff) << 24;
  i |= (getc(fp) & 0xff) << 16;
  i |= (getc(fp) & 0xff) << 8;
  i |= (getc(fp) & 0xff);

  return i;
}



static void putint32(i, fp)
     int   i;
     FILE *fp;
{
  putc( ((i>>24) & 0xff), fp);
  putc( ((i>>16) & 0xff), fp);
  putc( ((i>> 8) & 0xff), fp);
  putc( ((i) & 0xff), fp);
}

static int getint16(fp)
     FILE *fp;
{
  int i;

  i  = (getc(fp) & 0xff) << 8;
  i |= (getc(fp) & 0xff);

  return i;
}



static void putint16(i, fp)
     int   i;
     FILE *fp;
{
  putc( ((i>> 8) & 0xff), fp);
  putc( ((i) & 0xff), fp);
}

