/*
======================================================================
cvt.c

Ernie Wright  05 Feb 2012

Entry point for cvtmovie, a program to extract a frame sequence from
Amiga animation files created with pilbm and dilbm.
====================================================================== */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "iff.h"


/*
======================================================================
init_image()

Allocate and initialize the header and pixel array for a 24-bit BMP.

   w           width of the image in pixels
   h           height of the image
   dib         bitmap header (returned)
   dibsize     size of the header in bytes (returned)
   bits        pixel array (returned)
   bitsize     size of the pixel array in bytes (returned)
   rowbytes    number of bytes in one DIB scanline (returned)
====================================================================== */

static BOOL init_image( int w, int h, LPBITMAPINFOHEADER *dib, int *dibsize,
   unsigned char **bits, int *bitsize, int *rowbytes )
{
   *rowbytes = ( w * 3 + 3 ) & 0xFFFC;
   *dibsize = sizeof( BITMAPINFOHEADER );
   *bitsize = *rowbytes * h;

   *dib = calloc( 1, *dibsize );
   if ( !*dib ) return FALSE;

   *bits = calloc( 1, *bitsize );
   if ( !*bits ) {
      free( *dib );
      return FALSE;
   }

   ( *dib )->biSize          = sizeof( BITMAPINFOHEADER );
   ( *dib )->biWidth         = w;
   ( *dib )->biHeight        = h;
   ( *dib )->biPlanes        = 1;
   ( *dib )->biBitCount      = 24;
   ( *dib )->biCompression   = BI_RGB;
   ( *dib )->biSizeImage     = *bitsize;
   ( *dib )->biXPelsPerMeter = 0;
   ( *dib )->biYPelsPerMeter = 0;
   ( *dib )->biClrUsed       = 0;
   ( *dib )->biClrImportant  = 0;

   return TRUE;
}


/*
======================================================================
save_frame()

Save the movie's front image buffer as a 24-bit BMP.
====================================================================== */

static int save_frame( char *filename, MovieInfo *movie, LPBITMAPINFOHEADER dib,
   int dibsize, unsigned char *bits, int bitsize, int rowbytes )
{
   FILE *fp;
   BITMAPFILEHEADER bmf = { 0 };
   unsigned char *dst;
   int x, y;

   bmf.bfType = 'B' | ( 'M' << 8 );
   bmf.bfOffBits = sizeof( bmf ) + dibsize;
   bmf.bfSize = bmf.bfOffBits + bitsize;

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

   fwrite( &bmf, sizeof( bmf ), 1, fp );
   fwrite( dib, dibsize, 1, fp );

   for ( y = 0; y < movie->bmhd.h; y++ ) {
      GetRow24( movie, movie->bmhd.h - y - 1 );
      dst = bits + y * rowbytes;
      for ( x = 0; x < movie->bmhd.w; x++ ) {
         *dst++ = movie->rgb[ 2 ][ x ];
         *dst++ = movie->rgb[ 1 ][ x ];
         *dst++ = movie->rgb[ 0 ][ x ];
      }
   }

   fwrite( bits, bitsize, 1, fp );
   fclose( fp );

   return 1;
}


/*
======================================================================
write_ansq()

Write a text file with the contents of the movie's ANSQ chunk, which
lists the delta and duration of each frame.
====================================================================== */

static void write_ansq( MovieInfo *movie, char *outspec )
{
   FILE *fp;
   char filename[ 256 ];
   int i;

   sprintf( filename, "%s_ansq.txt", outspec );
   fp = fopen( filename, "w" );
   if ( !fp ) return;

   fprintf( fp, "   1     *     2\n" );
   fprintf( fp, "   2     0     2\n" );
   for ( i = 0; i < movie->nansqs; i++ )
      fprintf( fp, "%4d %5d %5d\n", i + 3, movie->ansq[ i ].dindex,
         movie->ansq[ i ].jiffies );
   fclose( fp );
}


/*
======================================================================
movie_error()

Print an error message when a movie fails to load.
====================================================================== */

static void movie_error( char *filename, int e )
{
   printf( "An error occurred while trying to load the movie file\n%s\n",
      filename );
   switch ( e ) {
      case EPNOMEM:
         printf( "Memory allocation failed.\n" );
         break;
      case EPNOFILE:
         printf( "Unable to open the file.\n" );
         break;
      case EPNOANIM:
         printf( "The file isn't an IFF ANIM.\n" );
         break;
      case EPNOILBM:
         printf( "The first frame wasn't found or wasn't valid.\n" );
         break;
      case EPNOBMHD:
         printf( "The bitmap header (BMHD) wasn't found.\n" );
         break;
      case EPNOBODY:
         printf( "The pixels (BODY) of the first frame weren't found.\n" );
         break;
      case EPPLANES:
         printf( "The number of bitplanes (more than 8) isn't supported.\n" );
         break;
      case EPDLTACT:
         printf( "Unable to count the delta chunks.\n" );
         break;
      case EPDLTARD:
         printf( "Unable to read the delta chunks.\n" );
         break;
      case EPNOANSQ:
         printf( "The animation sequence (ANSQ) chunk wasn't found.\n" );
         break;
      case EPFRAME1:
         printf( "The first frame couldn't be decoded.\n" );
         break;
      default:
         break;
   }
   printf( "The error may indicate that the movie file is corrupt.\n" );
}


/*
======================================================================
frame_filename()

Create a filename from a base name and a frame number.
====================================================================== */

static void frame_filename( char *spec, int frame, char *name )
{
   sprintf( name, "%s.%04.4d.bmp", spec, frame );
}


/*
======================================================================
user_prompt()

Show some movie statistics and ask the user whether to write the frame
sequence.  If the number of frames isn't sane, this gives the user a
chance to halt the program before it fills up his drive.
====================================================================== */

static int user_prompt( MovieInfo *movie )
{
   int ok;

   printf( "\n%d x %d, %d planes, ", movie->bmhd.w, movie->bmhd.h,
      movie->bmhd.nPlanes );
   if ( movie->camg & CAMG_HAM )
      printf( "HAM " );
   else if ( movie->camg & CAMG_EHB )
      printf( "Extra Halfbright " );
   else
      printf( "indexed color " );
   if ( movie->camg & CAMG_LACE )
      printf( "interlaced" );
   printf( "\n" );
   printf( "%d deltas, %d frames\n", movie->ndeltas, movie->nansqs + 2 );
   printf( "\nAbout to create %d BMP files.\n", movie->nansqs + 2 );
   printf( "Is that OK? (y/n) " );
   fflush( stdout );

   ok = fgetc( stdin );
   return ok == 'y' || ok == 'Y';
}


/*
======================================================================
convert()

Load the movie file and loop over the animation sequence (ANSQ) chunk
to generate image frames.
====================================================================== */

static int convert( char *infile, char *outspec )
{
   MovieInfo *movie;
   LPBITMAPINFOHEADER dib;
   int dibsize, bitsize, rowbytes, result, frame;
   unsigned char *bits;
   char filename[ 256 ];

   /* read the ANIM-J file */
   movie = ReadMovie( infile, &result );
   if ( result ) {
      movie_error( infile, result );
      return 0;
   }

   /* ask the user whether to write the frames */
   if ( !user_prompt( movie )) {
      FreeMovie( movie );
      return 0;
   }

   /* create the Windows DIB for the frames */
   if ( !init_image( movie->bmhd.w, movie->bmhd.h, &dib, &dibsize, &bits,
      &bitsize, &rowbytes )) {
      FreeMovie( movie );
      printf( "Couldn't allocate a DIB for the frames!\n" );
      return 0;
   }

   /* save the first frame */
   frame_filename( outspec, 1, filename );
   if ( !save_frame( filename, movie, dib, dibsize, bits, bitsize, rowbytes )) {
      printf( "Unable to save %s\n", filename );
      FreeMovie( movie );
      free( dib );
      free( bits );
      return 0;
   }
   printf( "%s\n", filename );

   /* save the second frame */
   ApplyDelta( movie, 0 );
   frame_filename( outspec, 2, filename );
   if ( !save_frame( filename, movie, dib, dibsize, bits, bitsize, rowbytes )) {
      printf( "Unable to save %s\n", filename );
      FreeMovie( movie );
      free( dib );
      free( bits );
      return 0;
   }
   printf( "%s\n", filename );

   /* save the rest of the frames */
   for ( frame = 3; frame < movie->nansqs + 3; frame++ ) {
      if ( movie->ansq[ frame - 3 ].jiffies < 0 ) {
         printf( "Loop detected.  Stopping now so that I don't create "
                 "an infinite number of frames!\n" );
         break;
      }
      ApplyDelta( movie, movie->ansq[ frame - 3 ].dindex );
      frame_filename( outspec, frame, filename );
      if ( !save_frame( filename, movie, dib, dibsize, bits, bitsize, rowbytes )) {
         printf( "Unable to save %s\n", filename );
         FreeMovie( movie );
         free( dib );
         free( bits );
         return 0;
      }
      printf( "%s\n", filename );
   }
   
   /* write the sequence file */
   write_ansq( movie, outspec );
   
   /* done */
   FreeMovie( movie );
   free( dib );
   free( bits );

   return 1;
}


/*
======================================================================
main()
====================================================================== */

int main( int argc, char *argv[] )
{
   if ( argc != 3 ) {
      printf( "\nusage:  %s <infile.movie> <outfile_spec>\n", argv[ 0 ] );
      printf( "Converts an Amiga ANIM-J movie to a 24-bit Windows BMP sequence.\n\n"
              "  infile.movie  the filename of the movie to convert\n"
              "  outfile_spec  the basename of the frames to create\n\n"
              "An outfile_spec of 'c:\\frames\\movie' will create frames "
              "'movie.0001.bmp', 'movie.0002.bmp', etc. in the c:\\frames "
              "directory.\n\n" );
      
      printf( "ANIM-J movies were created by programs called 'pilbm' and 'dilbm' "
              "and were played by a program called 'movie'.\n" );
      return 0;
   }
   convert( argv[ 1 ], argv[ 2 ] );
   return 0;
}
