/*
======================================================================
util.c

Ernie Wright  22 Mar 00

Utility routines for the LightWave IFF-ILBM loader/saver sample.
====================================================================== */

#include "iff.h"
#include <stdlib.h>
#include <math.h>


/*
=====================================================================
FreeILBM()

Free a PicInfo.

All PicInfos should be sent here at the end of their lives.  This
routine is written so that the resources tied to an incompletely
constructed PicInfo can be freed.  NULL is always echoed to the
caller.
===================================================================== */

PicInfo *FreeILBM( PicInfo *pic )
{
   if ( pic ) {
      if ( pic->fp ) fclose( pic->fp );
      if ( pic->filename ) free( pic->filename );
      if ( pic->cmap ) free( pic->cmap );
      if ( pic->buf ) free( pic->buf );
      if ( pic->body ) free( pic->body );
      free( pic );
   }
   return NULL;
}


/*
=====================================================================
find_aspect()

Search for the best numerator-denominator pair to express the aspect.

INPUTS
   a        floating-point aspect
   ax       storage for the numerator
   ay       storage for the denominator

RESULTS
   Finds the ratio of integers < 256 closest to the floating-point
   aspect and sets ax and ay.

The ILBM bitmap header expresses the pixel aspect as the ratio of two
unsigned chars.  We're looking for the best two here.
===================================================================== */

void find_aspect( float a, int *ax, int *ay )
{
   float d, dmin = 1e6f;
   int x, y;

   *ax = 1;
   *ay = 1;
   if ( a == 1.0f ) return;

   for ( y = 2; y < 256; y++ ) {
      x = ( int )( a * y );
      if ( x > 255 ) break;

      d = ( float ) fabs( a - ( float ) x / y );
      if ( d < dmin ) {
         dmin = d;
         *ax = x;
         *ay = y;
      }
      if ( d == 0.0f ) break;
   }
}


#ifdef _MSWIN
/*
=====================================================================
revbytes()

Reverses byte order in place.

INPUTS
   bp       bytes to reverse
   elsize   size of the underlying data type
   elcount  number of elements to swap

RESULTS
   Reverses the byte order in each of elcount elements.

IFF ILBMs use a byte order variously called "big-endian", "Motorola"
or "Internet order".  So do most systems.  The big exception is
Windows, which is where the ILBM code needs this function.  The
iff.h header substitutes an empty macro on other systems.
===================================================================== */

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;
   }
}
#endif


/*
=====================================================================
unpack()

Decompress a run-length encoded bitplane row.

INPUTS
   src      pointer to an array of encoded bytes
   dst      array of at least rowsize bytes to receive decoded data
   rowsize  number of bytes in an uncompressed row

RESULTS
   If successful, the decoded row is in dst, the src pointer is moved
   to the end of the encoded array, and the function returns TRUE.  If
   an error occurs, FALSE is returned.

The pixel data for an IFF ILBM is a byte stream containing code bytes
followed by data bytes.  The meaning of the code bytes, when regarded
as unsigned, is:

   n < 128     copy the next n+1 bytes
   n > 128     repeat the next byte 257-n times
   n = 128     no-op

Some versions of Photoshop incorrectly use the n = 128 no-op as a
repeat code, which breaks strictly conforming readers.  We allow the
use of n = 128 as a repeat.  This is pretty safe, since no one to my
knowledge explicitly writes no-ops into their ILBMs.  The reason
n = 128 is a no-op is historical:  the Mac Packbits buffer was only
128 bytes, and a repeat code of 128 generates 129 bytes.
===================================================================== */

int unpack( unsigned char **psrc, unsigned char *dst, int rowsize )
{
   int c, n;
   unsigned char *src = *psrc;

   while ( rowsize > 0 ) {
      n = *src++;

      if ( n < 128 ) {
         ++n;
         rowsize -= n;
         if ( rowsize < 0 ) return FALSE;
         while ( n-- ) *dst++ = *src++;
      }
      else {
         n = 257 - n;
         rowsize -= n;
         if ( rowsize < 0 ) return FALSE;
         c = *src++;
         while ( n-- ) *dst++ = c;
      }
   }
   *psrc = src;
   return TRUE;
}


/*
=====================================================================
unpack_ro()

Pretend to decompress a run-length encoded bitplane row, so that you
can move the source pointer.  Like unpack(), but doesn't write.
===================================================================== */

int unpack_ro( unsigned char **psrc, int rowsize )
{
   int n;
   unsigned char *src = *psrc;

   while ( rowsize > 0 ) {
      n = *src++;

      if ( n < 128 ) {
         ++n;
         rowsize -= n;
         if ( rowsize < 0 ) return FALSE;
         src += n;
      }
      else {
         n = 257 - n;
         rowsize -= n;
         if ( rowsize < 0 ) return FALSE;
         ++src;
      }
   }

   *psrc = src;

   return TRUE;
}


/*
=====================================================================
pack()

Run-length encode a bitplane row.

INPUTS
   src      pointer to an array bytes
   dst      array of at least rowsize bytes to receive encoded data
   rowsize  number of bytes in an uncompressed row

RESULTS
   Puts the encoded row in dst and returns the compressed length.

See unpack() for a description of the encoding.
===================================================================== */

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

int pack( unsigned char *src, unsigned char *dst, int rowsize )
{
   char c, lastc;
   int
      mode = DUMP,
      rstart = 0,
      putsize = 0,
      sp = 1,
      i;


   lastc = *src;
   rowsize--;

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

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

            if ( c == lastc ) {
               if (( sp - rstart ) >= MINRUN ) {
                  if ( rstart > 0 ) {
                     *dst++ = rstart - 1;
                     for ( i = 0; i < rstart; i++ )
                        *dst++ = *( src + 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 )) {
               *dst++ = rstart + 2 - sp;
               *dst++ = lastc;
               putsize += 2;
               src += sp - 1;
               sp = 1;
               rstart = 0;
               mode = DUMP;
            }
      }
      lastc = c;
   }

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

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

   return ( putsize );
}


/*
=====================================================================
bit rotation

Fast 90-degree bit rotation routines.

INPUTS
   src       starting address of 8 x 8 bit source tile
   srcstep   byte offset between adjacent rows in source
   dst       starting address of 8 x 8 bit destination tile
   dststep   byte offset between adjacent rows in destination

RESULTS
   Bits from the source are rotated 90 degrees either clockwise or
   counterclockwise and written to the destination.

Based on Sue-Ken Yap, "A Fast 90-Degree Bitmap Rotator," in GRAPHICS
GEMS II, James Arvo ed., Academic Press, 1991, ISBN 0-12-064480-0.
===================================================================== */

#define rtable( name, n ) \
   static unsigned int name[ 16 ] = { \
      0x00000000ul << n, 0x00000001ul << n, 0x00000100ul << n, 0x00000101ul << n, \
      0x00010000ul << n, 0x00010001ul << n, 0x00010100ul << n, 0x00010101ul << n, \
      0x01000000ul << n, 0x01000001ul << n, 0x01000100ul << n, 0x01000101ul << n, \
      0x01010000ul << n, 0x01010001ul << n, 0x01010100ul << n, 0x01010101ul << n };

rtable( rtab0, 0 )
rtable( rtab1, 1 )
rtable( rtab2, 2 )
rtable( rtab3, 3 )
rtable( rtab4, 4 )
rtable( rtab5, 5 )
rtable( rtab6, 6 )
rtable( rtab7, 7 )

#define extract( d, t ) \
   lonyb = *d & 0xF; hinyb = *d >> 4; \
   lo |= t[ lonyb ]; hi |= t[ hinyb ]; d += pstep;


/*
=====================================================================
bitrot_cw()

Rotate bits clockwise.  The ILBM loader uses this to convert pixel
bits from planar to chunky.
===================================================================== */

void bitrot_cw( unsigned char *src, int srcstep, unsigned char *dst, int dststep )
{
   unsigned char *p;
   int pstep, lonyb, hinyb;
   unsigned int lo, hi;

   lo = hi = 0;

   p = src; pstep = srcstep;
   extract( p, rtab0 )
   extract( p, rtab1 )
   extract( p, rtab2 )
   extract( p, rtab3 )
   extract( p, rtab4 )
   extract( p, rtab5 )
   extract( p, rtab6 )
   extract( p, rtab7 )

#define writebits_cw( d, w ) \
   *d = ( unsigned char )(( w >> 24 ) & 0xFF ); d += pstep; \
   *d = ( unsigned char )(( w >> 16 ) & 0xFF ); d += pstep; \
   *d = ( unsigned char )(( w >>  8 ) & 0xFF ); d += pstep; \
   *d = ( unsigned char )( w & 0xFF );

   p = dst; pstep = dststep;
   writebits_cw( p, hi )
   p += pstep;
   writebits_cw( p, lo )
}


/*
=====================================================================
bitrot_ccw()

Rotate bits counterclockwise.  The ILBM saver uses this to convert
pixel bits from chunky to planar.
===================================================================== */

void bitrot_ccw( unsigned char *src, int srcstep, unsigned char *dst, int dststep )
{
   unsigned char *p;
   int pstep, lonyb, hinyb;
   unsigned int lo, hi;

   lo = hi = 0;

   p = src; pstep = srcstep;
   extract( p, rtab7 )
   extract( p, rtab6 )
   extract( p, rtab5 )
   extract( p, rtab4 )
   extract( p, rtab3 )
   extract( p, rtab2 )
   extract( p, rtab1 )
   extract( p, rtab0 )

#define writebits_ccw( d, w ) \
   *d = ( unsigned char )( w & 0xFF );          d += pstep; \
   *d = ( unsigned char )(( w >>  8 ) & 0xFF ); d += pstep; \
   *d = ( unsigned char )(( w >> 16 ) & 0xFF ); d += pstep; \
   *d = ( unsigned char )(( w >> 24 ) & 0xFF );

   p = dst; pstep = dststep;
   writebits_ccw( p, lo )
   p += pstep;
   writebits_ccw( p, hi )
}
