/* Videospatial ------------ A small program for arbitrarily mixing the frames of video files output as a folder filled with TGA image files. External dependencies: libFreeImage.a, freeImage.h Bugs - using -t where number of frames > width of destbuffer TODO: procedural projection plane TODO: dynamic projection plane TODO: static video cube, dynamic projection plane Author: Sam Hinton, 2006 Copyright Sam Hinton 2006. This source code may be freely distributed and resused as long as the original author (Sam Hinton) is credited. */ #include #include #include #include #include "FreeImage.h" #include "videospatial.h" /* globals */ static args_t arguments; static FIBITMAP *pplane; static cache_t *cache; int main (int argc, const char *argv[]) { int error = 0; defaultArgs( &arguments ); error = parseArgs( argc, argv, &arguments ); if ( error ) { usage(); return( 10 + error ); } printf( "Initialising...\n" ); error = initialise( &arguments ); if ( error ) { printf( "Error %d: could not initialise the program.\n", 20 + error ); return( 20 + error ); } printf( "Processing image files (may take some time!)...\n" ); error = processImageFiles( arguments.rootFile, &arguments ); if ( error ) { printf( "Error %d: problem while processing image files.\n", 40 + error ); unloadProjectionPlane(); return( 40 + error ); } uninitialise( &arguments ); printf( "Done.\n" ); return 0; } /**************************************************************************************** Function: defaultArgs Purpose: set default values in args struct Parameters: args - a pointer to block of memory set aside for an args structure Returns: nothing ****************************************************************************************/ void defaultArgs( args_t *args ) { args->pgmFile[0] = 0; // must be set on command line args->rootFile[0] = 0; // must be set on command line args->width = 0xFFFFFFFF; // autoset if not specified on command line args->height = 0xFFFFFFFF; // as above args->startFile = 1; args->endFile = 256; args->ppMin = 0; // unused args->ppMax = 255; // unused args->space = TRUE; // TRUE if filenames have space between basename and index strcpy (args->outFolder, "output" ); // optional output folder args->pad = -1; args->transpose = 0; args->cacheSize = CACHE_TOTAL_FRAMES; } /* Function: parseArgs Purpose: convert command line arguments into values in the args struct Parameters: two dimensional array of chars for command line string (argc), number of args (argv) Returns: 0 on success, non-zero on fail */ int parseArgs( int argc, const char *argv[], args_t *args ) { int i; if ( argc < 2 ) { return 1; // no arguments given } strncpy( args->rootFile, argv[1], 50 ); strncpy( args->pgmFile, argv[2], 50 ); for (i=3; ispace = TRUE; } else if ( strncmp( argv[i], "-w", 2 )==0 ) { args->width = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-h", 2 )==0 ) { args->height = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-s", 2 )==0 ) { args->startFile = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-e", 2 )==0 ) { args->endFile = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-o", 2 )==0 ) { strncpy( args->outFolder, argv[i+1], 49 ); i++; } else if ( strncmp( argv[i], "-p", 2 )==0 ) { args->pad = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-t", 2 )== 0) { args->transpose = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-a", 2 )==0 ) { args->accum = atoi( argv[i+1] ); i++; } else if ( strncmp( argv[i], "-c", 2)==0 ) { args->cacheSize = atoi( argv[i+1] ); printf( "Cache size set to %d.\n", args->cacheSize ); i++; } } // set filename index numeric pad based on endfile if ( args->pad == -1 ) // -1 if not explicitly set with -p { if ( args->endFile > 9999 ) { args->pad = 5; } else if ( args->endFile > 999 ) { args->pad = 4; } else if ( args->endFile > 99 ) { args->pad = 3; } else { args->pad = 2; } } if ( args->space ) { strcat( args->rootFile, " " ); } return 0; } /* Function: usage(void) Purpose: print out usage instructions to stdout Parameters: none Returns: nothing */ void usage( void ) { printf ("Usage:\n" ); printf (" videowarp [-space] [-o foldername] [-s start] [-e end] [-w width] [-h height] [-p pad]\n" ); printf (" -space: add space between filename and index number\n (default = yes)\n" ); printf (" -o : alternate output folder (default = output)\n" ); printf (" -s : first frame to process (default = 1)\n" ); printf (" -e : last frame to process (default = 256)\n" ); printf (" -w : override width (default is taken from first frame)\n" ); printf (" -h : override height (default is taken from first frame)\n" ); printf (" -p : number of digits in file index number (eg: pad 3 is 001, 4 is 0001)\n" ); printf (" -t : transpose horizontal (1) or vertical (2) (default = 0, no transpose)\n" ); printf (" -a : turn on accumulator function (accum should be 1, default = 0, no accum)\n" ); printf (" -c : set cache size in frames (default=256)\n"); } /* Function: initCache Purpose: allocate storage space for the frame cache Parameters: a pointer to a valid args structure Returns: 0 on success, non-zero on error */ int initcache( args_t *args ) { int i, end, j = 0, cind; char filename[50]; filename[0] = 0; // allocate memory for the cache cache = new cache_t[ args->cacheSize ]; if ( args->endFile > args->cacheSize ) { end = args->cacheSize; } else { end = args->endFile; } // preload images into cache for ( i=args->startFile; istartFile + end; i++ ) { cind = j % args->cacheSize; ifname( args->rootFile, i, filename, args->pad ); cache[cind].frame = FreeImage_Load( FIF_TARGA, filename, 0 ); if ( cache[cind].frame == NULL ) { printf( " Error: couldn't preload file %s into cache.\n", filename ); return 1; } j++; // progress bar if ( (j % 25) == 0 ) { fwrite( ".", 1, 1, stdout ); fflush( stdout ); } } printf( "OK.\n" ); return 0; } /* Function: recache Purpose: removes a frame from the cache and loads in a new frame Parameters: frame - the frame to load in; end - the last frame to process; basename - string basename to load Returns: nothing */ int recache( int frame, args_t *args ) { char filename[50]; int index; filename[0] = 0; index = frame % args->cacheSize; FreeImage_Unload( cache[ index ].frame ); cache[ index ].frame = NULL; if ( frame >= args->endFile ) { return 0; } // load in next frame ifname( args->rootFile, frame, filename, args->pad ); cache[ index ].frame = FreeImage_Load( FIF_TARGA, filename, 0 ); if ( cache[ index ].frame == NULL ) { // error: can't load next frame printf( "Error: can't load new frame %s into cache.\n", filename ); return 1; } return 0; } /* Function: decache Purpose: unload all imaged from the cache Parameters: none Returns: nothing */ void decache( args_t *args ) { int i; for ( i=0; icacheSize; i++ ) { if ( cache[i].frame ) FreeImage_Unload( cache[i].frame ); } delete [] cache; } /* Function: ifname Purpose: appends and index and ".tga" to a base filename Parameters: basename, index, filename - pointer to char array to save filename in - must be at least basename + 7 bytes long Returns: nothing */ void ifname( const char *basename, int index, char *filename, int pad ) { // adjust padding for larger and smaller ranges switch( pad ) { case 5: sprintf( filename, "%s%05d.tga", basename, index ); break; case 4: sprintf( filename, "%s%04d.tga", basename, index ); break; case 3: sprintf( filename, "%s%03d.tga", basename, index ); break; default: sprintf( filename, "%s%02d.tga", basename, index ); break; } } /* Function: initialise Purpose: initialise data structures and allocate memory Parameters: args - a pointer to a properly initialised arg_s struct Returns: 0 on success, non-zero on error */ int initialise( args_t *args ) { char filename[50]; char number[4]; int i, error, depth; number[0] = 0; filename[0] = 0; FreeImage_Initialise( TRUE ); if ( args->width == -1 || args->height == -1 ) { // load first file to get width and height if not specified on command line FIBITMAP *first = NULL; ifname( args->rootFile, args->startFile, filename, args->pad ); first = FreeImage_Load( FIF_TARGA, filename, 0 ); if ( first == NULL ) { printf( " Error loading first frame: %s\n", filename ); return 1; } if ( args->transpose ) { // if args->transpose is set, width or height needs to be swapped with depth if ( args->transpose == 1 ) { // width and depth get swapped args->width = args->endFile - args->startFile; args->height = FreeImage_GetHeight( first ); if ( args->cacheSize < FreeImage_GetWidth( first ) ) { printf( "Cache too small to transpose this image sequence (must be at least %d)\n", FreeImage_GetWidth( first ) ); return( 4 ); } } else { // transpose = 2; height and depth get swapped args->width = FreeImage_GetWidth( first ); args->height = args->endFile - args->startFile; if ( args->cacheSize < FreeImage_GetHeight( first ) ) { printf( "Cache too small to transpose this image sequence (must be at least %d)\n", FreeImage_GetHeight( first ) ); return( 5 ); } } } else // no transpose - first frame gives width and height { args->width = FreeImage_GetWidth( first ); args->height = FreeImage_GetHeight( first ); } FreeImage_Unload( first ); printf( "Output dimensions will be: x=%d, y=%d.\n", args->width, args->height); } // done setting width and height parms // load projection plane pplane = NULL; printf( " Loading projection plane (%s)...\n", arguments.pgmFile ); error = loadProjectionPlane( arguments.pgmFile, arguments.width, arguments.height ); if ( error ) { printf( " Error %d: Could not load projection plane.\n", error ); return( 2 ); } // initialise the image cache printf( " Preloading image cache (please wait)" ); error = initcache( args ); if ( error ) { return 3; } return 0; } void uninitialise( args_t *args ) { unloadProjectionPlane(); decache( args ); // release all memory allocated to images in cache - A LOT FreeImage_DeInitialise(); } /* Function: loadProjectionPlane( char *pgmfile ) Purpose: loads a PGM file that acts as a register of offsets when processing video Parameters: pgmfile - filename of PGM to load; (pplane is a global) Returns: 0 on success, non-zero on error */ int loadProjectionPlane( char *pgmfile, int aWidth, int aHeight ) { int x, y, width, height; BYTE index; pplane = FreeImage_Load( FIF_GIF, pgmfile, 0 ); if ( pplane == NULL ) return 1; width = FreeImage_GetWidth( pplane ); height = FreeImage_GetHeight( pplane ); if ( width != aWidth ) { printf( " ** Warning: projection plane width does not match frame width.\n" ); return 0; } if ( height != aHeight ) { printf( " ** Warning: projection plane height does not match frame height.\n" ); return 0; } return 0; } /* Function: unloadProjectPlane() Purpose: frees memory associated with a projection plane Parameters: none ( sets a global ) Returns: nothing */ void unloadProjectionPlane( void ) { if ( pplane ) { FreeImage_Unload( pplane ); } } /* Function: updatePPlane Purpose: changes pixel data in projection plane Parameters: args, fr - current input frame number Returns: nothing */ //void updatePPlane( args_t *args_t, int frame ) //{ // particle engine... etc. //} /* Function: processImageFiles Purpose: creates frame files Parameters: basename - filename base;start - first frame to process;end - last frame to process Returns: 0 on success, non-zero on error */ int processImageFiles( char *basename, args_t *args ) { /* - allocate memory for destFrame - for each frame - for each pixel in destFrame: - sourceframe = pgmpixel[x][y] - if sourceframe out of bounds set destframe[x][y] black; continue - load source frame - destframe[x][y] = sourceframe[x][y] - save destFrame buffer - dealloc destFrame */ int fr, x, y, index; BYTE sourceFrame; char filename[50], saveFilename[50], saveBase[50], progress[50]; FIBITMAP *destFrame = NULL; RGBQUAD pixel, dpixel, lpixel; int accum_n = 1; float dR, dG, dB; float oldGrey, newGrey, dGrey; //number[0] = 0; filename[0] = 0; saveFilename[0] = 0; saveBase[0] = 0; if ( args->outFolder[0] != 0 ) { // append output folder to save filename if set strcat( saveBase, args->outFolder ); strcat( saveBase, "/" ); } // allocate memory for the output frame - we'll reuse this instead of reallocating and deallocating destFrame = FreeImage_Allocate( args->width, args->height, 24, 0, 0, 0 ); // 24 bpp if ( !destFrame ) return 1; // error: can't allocate memory for destFrame // loop through all input frames to produce output frames for( fr=args->startFile; frendFile; fr++ ) // add n to endFile to push through to black (n=pplane max offset) { // unload any images from cache we won't need for this frame and load in any we will need if ( !args->transpose ) // don't recache if we're transposing { if ( recache( fr + 255, args ) != 0 ) { // the +255 accounts for the precaching of images // error refreshing cache - normally image load error FreeImage_Unload( destFrame ); destFrame = NULL; return 2; } } // TODO: update pplane image - eg: load in new image frame from animated GIF // or do some kind of procedural manipulation of data // updatePPlane( args ); // process pixels in the pplane for ( y=0; yheight; y++ ) { for ( x=0; xwidth; x++ ) { if ( !FreeImage_GetPixelIndex( pplane, x, y, &sourceFrame ) ) // sourceFrame set here from GIF { // error: couldn't get pixel index from pplane - not 8-bit file, or x/y too large? free ( destFrame ); destFrame = NULL; return 3; } sourceFrame += fr; // source(input) frame is this frame (fr) plus the index from the pplane index = sourceFrame % args->cacheSize; // calculate the index in the cache that corresponds with the input frame if ( cache[index].frame == NULL ) { pixel.rgbRed = 0; // undefined pixels set to black pixel.rgbGreen = 0; // undefined pixels set to black pixel.rgbBlue = 0; // undefined pixels set to black } else { if ( args->transpose == 1 ) // horizontal transpose { if ( x > args->cacheSize || fr > args->width ) { printf( "Transpose error: width not symmetrical with depth.\n" ); return( 5 ); } FreeImage_GetPixelColor( cache[x].frame, index, y, &pixel ); } else if ( args->transpose == 2 ) // vertical transpose { if ( y > args->cacheSize || fr > args->height ) { printf( "Transpose error: height not symmetrical with depth.\n" ); return( 6 ); } FreeImage_GetPixelColor( cache[y].frame, x, index, &pixel ); } else // no transpose { FreeImage_GetPixelColor( cache[index].frame, x, y, &pixel ); } } if ( args->accum ) { if ( accum_n == 0 ) continue; // get pixel value from previous frame if ( index > 0 ) { FreeImage_GetPixelColor( cache[index-1].frame, x, y, &dpixel ); FreeImage_GetPixelColor( destFrame, x, y, &lpixel ); } else { FreeImage_GetPixelColor( cache[args->cacheSize-1].frame, x, y, &dpixel ); FreeImage_GetPixelColor( destFrame, x, y, &lpixel ); } if ( args->accum == 1 ) // redundant for now, but ready for expansion in future { // calculate weighted average, assuming dpixel represents n-1 iteration of the func dR = (float)((float)dpixel.rgbRed * (float)(accum_n-1) + (float)pixel.rgbRed) / (float)accum_n; dG = (float)((float)dpixel.rgbGreen * (float)(accum_n-1) + (float)pixel.rgbGreen) / (float)accum_n; dB = (float)((float)dpixel.rgbBlue * (float)(accum_n-1) + (float)pixel.rgbBlue) / (float)accum_n; pixel.rgbRed = (unsigned char)roundf(dR); pixel.rgbGreen = (unsigned char)roundf(dG); pixel.rgbBlue = (unsigned char)roundf(dB); } else if ( args->accum == 2 ) { // dpixel = previous frame's pixel value // pixel = current frame's pixel value // lpixel = the pixel value of the destFrame from the last iteration oldGrey = (((float)dpixel.rgbRed) + ((float)dpixel.rgbGreen) + ((float)dpixel.rgbBlue)) / 3.0f; newGrey = (((float)pixel.rgbRed) + ((float)pixel.rgbGreen) + ((float)pixel.rgbBlue)) / 3.0f; dGrey = fabs( oldGrey - newGrey ); dGrey /= 255.0f; // little change? -> set to grey if ( dGrey > 0.5f ) { pixel.rgbRed = lpixel.rgbRed/2; pixel.rgbGreen = lpixel.rgbGreen/2; pixel.rgbBlue = lpixel.rgbBlue/2; } dR = (float)(abs(dpixel.rgbRed - pixel.rgbRed)); dG = (float)(abs(dpixel.rgbGreen - pixel.rgbGreen)); dB = (float)(abs(dpixel.rgbBlue - pixel.rgbBlue)); dR /= 255.0f; dG /= 255.0f; dB /= 255.0f; //pixel.rgbRed *= (unsigned char)((dR + dG + dB) / 3.0); //pixel.rgbGreen *= (unsigned char)((dR + dG + dB) / 3.0); //pixel.rgbBlue *= (unsigned char)((dR + dG + dB) / 3.0); } FreeImage_SetPixelColor( destFrame, x, y, &pixel ); } else // no accum, so just set pixel unmodified { FreeImage_SetPixelColor( destFrame, x, y, &pixel ); } } // y } // x // construct filename and save it ifname( saveBase, fr, saveFilename, args->pad ); if ( !FreeImage_Save( FIF_TARGA, destFrame, saveFilename, 0 ) ) { // error saving file printf( "Couldn't save %s.\n", saveBase ); return 4; } saveFilename[0] = 0; // reset saveFilename for next iteration accum_n++; // increment accumulator counter (not used if not in accummode) if ( (fr % 25)==0 ) { sprintf( progress, "%d", fr ); fwrite( progress, 1, strlen( progress ), stdout ); } fwrite( ".", 1, 1, stdout ); fflush( stdout ); } if ( destFrame ) { FreeImage_Unload( destFrame ); destFrame = NULL; } printf( "\n" ); return 0; } /* * init * load PGM * process image files: - allocate memory for destFrame - for each frame - for each pixel in destFrame: - sourceframe = pgmpixel[x][y] - if sourceframe out of bounds set destframe[x][y] black; continue - load source frame - destframe[x][y] = sourceframe[x][y] - save destFrame buffer - dealloc destFrame * cleanup - deallocate PGM memory * process image files (cache method): - allocate memory for destFrame - for each frame - for each pixel in destFrame: - sourceframe = pgmpixel[x][y] - if sourceframe out of bounds set destframe[x][y] black; continue - if sourceframe is cached: - destframe[x][y] = cache[frameID].pixel[x][y] - cache[frameID].lastUsed = currentLoopCount++; - continue; - else - load source frame into spare cache slot - if no cache slots, use slot where lastUsed is smallest - destframe[x][y] = cache[frameID].pixel[x][y] - save destFrame buffer - dealloc destFrame * process image files ( precache method ): - determine highest frequency frame files for the current frame - load all frames into cache if below tolerance OR load highest frequency if above tolerance */