/*
======================================================================
jug2tga.c

Ernie Wright  15 Feb 98
MSVC 4.0

Extract frames from Eric Graham's original Juggler animation.

The Juggler was an Amiga demonstration program written in 1986, very
early in the history of the Amiga, and shown on Amiga 1000 computers
running in dealer windows.  That's where I first saw it.  The image
data was stored in an undocumented format invented by Eric for this
animation--standard Amiga animation formats and the tools to support
them were a couple of years away.

The format of Juggler's movie.data file is

   number of frames     long                      24
   image width          short                    320
   image height         short                    200
   HAM palette          {byte r,g,b}[ 16 ]
   first frame          byte[ 48000 ]
   delta frames         various
      delta size        long
      section           various[ 5 ]
         section size   long
         run count      short
         run            various[ run count ]
            offset      short
            data        byte[ 60, 48, 36, 24 or 12 ]

Each delta frame is divided into 5 sections.  The first section has
runs of 10 bytes x 6 bitplanes, the second section, 8 bytes x 6, and
the others 6 x 6, 4 x 6, and 2 x 6.  Bitplane data is contiguous.
====================================================================== */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#pragma pack( 1 )


typedef struct { unsigned char red, green, blue; } RGB;

typedef struct {
   long           nframes;
   short          width;
   short          height;
   RGB            pal[ 16 ];
} MOVIE;

typedef struct {
   unsigned char  idlen;
   unsigned char  cmaptype;
   unsigned char  imtype;
   unsigned short cmapstart;
   unsigned short cmaplen;
   unsigned char  cmapdepth;
   short          xorigin;
   short          yorigin;
   unsigned short imwidth;
   unsigned short imheight;
   unsigned char  pixdepth;
   unsigned char  imdescrip;
} TGA;


void rotate8x8( unsigned char *src, int srcstep, unsigned char *dst, int dststep );


void rev_bytes( void *bp, int elsize, int elcount )
{
   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;
   }
}


int write_tgahdr( MOVIE *mov, FILE *fp )
{
   TGA hdr = { 0 };

   hdr.imwidth   = mov->width;      // image width
   hdr.imheight  = mov->height;     // image height
   hdr.imdescrip = 0x20;            // origin is upper left
   hdr.imtype    = 2;               // 24-bit color
   hdr.pixdepth  = 24;              // 24 bits per pixel

   return 1 == fwrite( &hdr, 18, 1, fp );
}


unsigned char
   frame0 [ 48000 ],
   frame1 [ 48000 ],
   buf    [ 48000 ],
   index  [   320 ],
   rgb    [   960 ];


static void unHAM( MOVIE *mov, unsigned char *src, unsigned char *dst )
{
   RGB prev;
   int i, j, hbits, mbits, mask;


   prev.red = prev.green = prev.blue = 0;
   hbits = 4;
   mbits = 8 - hbits;
   mask  = ( 1 << hbits ) - 1;

   for ( i = 0; i < mov->width; i++ ) {
      j = src[ i ];
      switch ( j >> hbits ) {
         case 0:
            dst[ 0 ] = mov->pal[ j & mask ].blue;
            dst[ 1 ] = mov->pal[ j & mask ].green;
            dst[ 2 ] = mov->pal[ j & mask ].red;
            break;

         case 1:
            dst[ 0 ] = (( j & mask ) << mbits ) | ( j & mask );
            dst[ 1 ] = prev.green;
            dst[ 2 ] = prev.red;
            break;

         case 2:
            dst[ 0 ] = prev.blue;
            dst[ 1 ] = prev.green;
            dst[ 2 ] = (( j & mask ) << mbits ) | ( j & mask );
            break;

         case 3:
            dst[ 0 ] = prev.blue;
            dst[ 1 ] = (( j & mask ) << mbits ) | ( j & mask );
            dst[ 2 ] = prev.red;
            break;
      }
      prev.red   = dst[ 2 ];
      prev.green = dst[ 1 ];
      prev.blue  = dst[ 0 ];
      dst += 3;
   }
}


int save_frame( MOVIE *mov, int n )
{
   FILE *fp;
   char name[ 64 ];
   unsigned char *b, *f;
   int i, j, k;

   sprintf( name, "juglr%03.3d.tga", n );
   fp = fopen( name, "wb" );
   if ( !fp ) {
      printf( "Couldn't open %s.\n", name );
      return 0;
   }

   if ( !write_tgahdr( mov, fp )) {
      printf( "Couldn't write %s TGA header.\n", name );
      return 0;
   }

   f = (( n - 1 ) % 2 ) ? frame1 : frame0;
   memset( buf, 0, 320 );
   for ( j = 0; j < 200; j++ ) {
      memcpy( buf, f + j * 240, 240 );
      for ( i = 0, k = 0, b = buf; i < 40; i++, b++, k += 8 )
         rotate8x8( b, 40, index + k, 1 );
      unHAM( mov, index, rgb );
      fwrite( rgb, 320, 3, fp );
   }

   fclose( fp );

   return 1;
}


int frame_first( MOVIE *mov, FILE *fp )
{
   int i, j, k, n;

   for ( i = 0; i < 6; i++ ) {
      fread( buf, 8000, 1, fp );
      k = 40 * i;
      n = 0;
      for ( j = 0; j < 200; j++, k += 240, n += 40 )
         memcpy( &frame0[ k ], &buf[ n ], 40 );
   }

   memcpy( frame1, frame0, 48000 );

   return save_frame( mov, 1 );
}


int frame_next( MOVIE *mov, FILE *fp, int fnum )
{
   unsigned long size;
   unsigned short c, offset;
   unsigned char *b, *f;
   int i, j, k, w, x, y;


   f = (( fnum - 1 ) % 2 ) ? frame1 : frame0;

   fread( &size, 4, 1, fp );
   rev_bytes( &size, 4, 1 );
   fread( buf, size, 1, fp );

   b = buf;
   for ( i = 0; i < 5; i++ ) {
      b += 4;
      w = 10 - 2 * i;
      memcpy( &c, b, 2 );
      rev_bytes( &c, 2, 1 );
      b += 2;

      for ( j = 0; j < c; j++ ) {
         memcpy( &offset, b, 2 );
         rev_bytes( &offset, 2, 1 );
         b += 2;

         y = offset / 40;
         x = offset - y * 40;
         for ( k = 0; k < 6; k++ ) {

            /* At least one of the deltas apparently wraps around to
               the next scanline.  This <if> is used to eliminate an
               artifact caused by the wraparound behavior. */

            if ( x >= 0 && x + w <= 40 )
               memcpy( f + y * 240 + k * 40 + x, b, w );
            b += w;
         }
      }
   }

   return save_frame( mov, fnum );
}


int main( int argc, char *argv[] )
{
   FILE *fp;
   MOVIE mov;
   int i;


   if ( argc != 2 ) {
      printf( "Usage:  %s movie.dat\n", argv[ 0 ] );
      return 0;
   }

   fp = fopen( argv[ 1 ], "rb" );
   if ( !fp ) {
      printf( "Couldn't open %s\n", argv[ 1 ] );
      return 0;
   }

   fread( &mov, sizeof( MOVIE ), 1, fp );
   rev_bytes( &mov.nframes, 4, 1 );
   rev_bytes( &mov.width, 2, 2 );

   for ( i = 0; i < 16; i++ ) {
      mov.pal[ i ].red *= 17;
      mov.pal[ i ].green *= 17;
      mov.pal[ i ].blue *= 17;
   }

   if ( !frame_first( &mov, fp )) {
      printf( "Couldn't save frame 1.\n" );
      fclose( fp );
      return 0;
   }

   for ( i = 1; i < mov.nframes; i++ )
      if ( !frame_next( &mov, fp, i + 1 )) {
         printf( "Couldn't save frame %d.\n", i + 1 );
         fclose( fp );
         return 0;
      }

   fclose( fp );
   return 0;
}
