/* ====================================================================== 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 #include #include #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 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; }