/*
======================================================================
iff.c

Ernie Wright  18 Apr 98
MSVC 4.0

Save an image in IFF ILBM format.  Supports 24-bit color and 8-bit
grayscale.
====================================================================== */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#define ID_FORM MAKE_ID('F','O','R','M')
#define ID_ILBM MAKE_ID('I','L','B','M')
#define ID_BMHD MAKE_ID('B','M','H','D')
#define ID_BODY MAKE_ID('B','O','D','Y')


typedef struct {
   unsigned short w, h;                   //  image width, height in pixels
   short          x, y;                   //  image position in destination
   unsigned char  nPlanes;                //  number of bitplanes (depth)
   unsigned char  masking;                //  mask type
   unsigned char  compression;            //  compression algorithm
   unsigned char  pad1;                   //  pad char for alignment
   unsigned short transparentColor;       //  a logical color number
   unsigned char  xAspect, yAspect;       //  pixel ratio of width : height
   short          pw, ph;                 //  source page size
} BMHD;


void rotate8x8( unsigned char *src, int srcstep, unsigned char *dst, int dststep );
void image_progress( int y );

static int  writeBMHD( FILE *fp, int w, int h, int nchannels );
static int  writeBODY( FILE *fp, char *buf, int w, int h, int span, int nchannels );
static int  write_channel( FILE *fp, unsigned char *src, unsigned char *rot,
               unsigned char *dst, int rowbytes );
static int  pack( char *src, char *dst, int rowsize );
static void revbytes( void *bp, int elsize, int elcount );


/*
======================================================================
saveIFF()

Save an image in IFF ILBM format.

INPUTS
   filename       name of the file to create
   buf            pixels in packed DIB arrangement
   w, h           width and height
   span           number of bytes in one image row
   nchannels      1 for grayscale, 3 for RGB

RESULTS
   Saves the image.  Returns TRUE if successful, otherwise FALSE.
====================================================================== */

int saveIFF( char *filename, char *buf, int w, int h, int span,
   int nchannels )
{
   FILE *fp;
   long ck[ 3 ] = { ID_FORM, 0, ID_ILBM };
   int size, ok = 0;

   fp = fopen( filename, "wb" );
   if ( !fp ) return 0;

   while ( 1 ) {
      if ( 1 != fwrite( ck, 12, 1, fp )) break;
      ck[ 1 ] = 4;
      ck[ 1 ] += size = writeBMHD( fp, w, h, nchannels );
      if ( !size ) break;
      ck[ 1 ] += size = writeBODY( fp, buf, w, h, span, nchannels );
      if ( !size ) break;

      revbytes( &ck[ 1 ], 4, 1 );

      if ( fseek( fp, 4, SEEK_SET )) break;
      if ( 1 != fwrite( &ck[ 1 ], 4, 1, fp )) break;
      ok = 1;
      break;
   }

   fclose( fp );
   if ( !ok ) remove( filename );
   return ok;
}


static int writeBMHD( FILE *fp, int w, int h, int nchannels )
{
   BMHD bmhd = { w, h, 0, 0, nchannels * 8, 0, 1, 0, 0, 1, 1, w, h };
   long ck[ 2 ] = { ID_BMHD, 20 };

   revbytes( &ck[ 1 ], 4, 1 );
   revbytes( &bmhd.w, 2, 4 );
   revbytes( &bmhd.pw, 2, 2 );

   fwrite( ck, 8, 1, fp );
   fwrite( &bmhd, 20, 1, fp );

   return 28;
}


static int writeBODY( FILE *fp, char *bits, int w, int h, int span,
   int nchannels )
{
   unsigned char *mem, *row, *src, *rot, *dst;
   int pos, i, x, y, n, rowbytes, size = 0;
   long ck[ 2 ] = { ID_BODY, 0 };


   pos = ftell( fp ) + 4;
   fwrite( ck, 8, 1, fp );

   rowbytes = (( w + 15 ) >> 4 ) << 1;
   mem = calloc( 32 * rowbytes, 1 );
   if ( !mem ) return 0;

   src = mem;
   rot = src + 8 * rowbytes;
   dst = rot + 8 * rowbytes;

   image_progress( h );
   for ( y = 0; y < h; y++ ) {
      row = bits + ( h - ( y + 1 )) * span;
      if ( nchannels == 3 ) {
         for ( i = 2; i >= 0; i-- ) {
            for ( x = 0; x < w; x++ )
               src[ x ] = row[ 3 * x + i ];
            n = write_channel( fp, src, rot, dst, rowbytes );
            if ( n == 0 ) { free( mem ); return 0; }
            size += n;
         }
      }
      else {
         n = write_channel( fp, row, rot, dst, rowbytes );
         if ( n == 0 ) { free( mem ); return 0; }
         size += n;
      }
      image_progress( 0 );
   }

   free( mem );

   if ( size & 1 ) fputc( 0, fp );
   ck[ 1 ] = size;
   size += ( size & 1 );

   revbytes( &ck[ 1 ], 4, 1 );

   fseek( fp, pos, SEEK_SET );
   fwrite( &ck[ 1 ], 4, 1, fp );
   fseek( fp, size, SEEK_CUR );

   return size + 8;
}


static int write_channel( FILE *fp, unsigned char *src, unsigned char *rot,
   unsigned char *dst, int rowbytes )
{
   unsigned char *p, *q;
   int i, x, n, size;

   p = src;
   q = rot;
   for ( x = 0; x < rowbytes; x++ ) {
      rotate8x8( p, 1, q, rowbytes );
      p += 8;
      q++;
   }

   p = rot;
   q = dst;
   n = size = 0;
   for ( i = 0; i < 8; i++ ) {
      size += n = pack( p, q, rowbytes );
      p += rowbytes;
      q += n;
   }

   if ( 1 != fwrite( dst, size, 1, fp )) return 0;
   return size;
}


static void revbytes( void *bp, int elsize, int elcount )
{
   register unsigned char *p, *q;

   p = ( unsigned char * ) bp;

   if ( elsize == 2 ) {
      q = p + 1;
      while ( elcount-- ) {
         *p ^= *q;
         *q ^= *p;
         *p ^= *q;
         p += 2;
         q += 2;
      }
      return;
   }

   while ( elcount-- ) {
      q = p + elsize - 1;
      while ( p < q ) {
         *p ^= *q;
         *q ^= *p;
         *p ^= *q;
         ++p;
         --q;
      }
      p += elsize >> 1;
   }
}


#define DUMP    0
#define RUN     1
#define MINRUN  3
#define MAXRUN  128
#define MAXDUMP 128

static int pack( char *source, char *dest, int rowsize )
{
   char c, lastc;
   int
      mode = DUMP,
      rstart = 0,
      putsize = 0,
      sp = 1,
      i;


   lastc = *source;
   rowsize--;

   while ( rowsize > 0 ) {
      c = *( source + sp );
      sp++;
      rowsize--;

      switch ( mode ) {
         case DUMP:
            if ( sp > MAXDUMP ) {
               *dest++ = sp - 2;
               for ( i = 0; i < sp - 1; i++ )
                  *dest++ = *( source + i );
               putsize += sp;
               source += sp - 1;
               sp = 1;
               rstart = 0;
               break;
            }

            if ( c == lastc ) {
               if (( sp - rstart ) >= MINRUN ) {
                  if ( rstart > 0 ) {
                     *dest++ = rstart - 1;
                     for ( i = 0; i < rstart; i++ )
                        *dest++ = *( source + i );
                     putsize += rstart + 1;
                  }
                  mode = RUN;
               }
               else if ( rstart == 0 ) mode = RUN;
            }
            else rstart = sp - 1;
            break;

         case RUN:
            if (( c != lastc ) || ( sp - rstart > MAXRUN )) {
               *dest++ = rstart + 2 - sp;
               *dest++ = lastc;
               putsize += 2;
               source += sp - 1;
               sp = 1;
               rstart = 0;
               mode = DUMP;
            }
      }
      lastc = c;
   }

   switch ( mode ) {
      case DUMP:
         *dest++ = sp - 1;
         for ( i = 0; i < sp; i++ )
            *dest++ = *( source + i );
         putsize += sp + 1;
         break;

      case RUN:
         *dest++ = rstart + 1 - sp;
         *dest   = lastc;
         putsize += 2;
   }

   return ( putsize );
}
