// fltrppm4.cpp // Copyright (c) Menno Rubingh 2016. Web: [http://rubinghsoftware.de] // // MR Mar 2016. // // Change the value of each pixel in a "P6" PPM file (each pixel independently // of the other pixels), according to the Reverse Polish expression specified as // a command line argument. // The input PPM image is read from stdin, the output PPM image is written to // stdout. The size of the image remains unchanged. // //--- // Design notes: // // - The individual operations (+ - * ...) in the RP expression do not clip, but // the value of the r,g,b output variable is clipped to the interval [0.0, 1.0] // after reading the output variable from the RP interpreter. // // - Checking for domain errors during expression evaluation is done in two ways: // 1) The whole series of evaluate() calls for ALL pixels in the image is bracketed // between one single feclearexcept() ... fetestexcept() pair, checking for a // domain error for any of the pixels in the whole image. // 2) Selectable by command-line option, optionally also check for domain errors // every time an r,g,b output variable value is read. This costs a (very) small // (negligibly small) time for every pixel, but preempts any partial nonsense // data being written to the output PPM file. // //--- // Uses: // rpinterpreter.h, } -- Class RPInterpreter. // rpinterpreter.a } // llgp_between.h -- BETWEEN_II(). // //--- // Build: // g++ -Wall -ggdb -o fltrppm4 fltrppm4.cpp rpinterpreter.a -lm // #include "rpinterpreter.h" //RPInterpreter. #include "llgp_between.h" //BETWEEN_II(). #include <assert.h> #include <stdio.h> #include <stdlib.h> //exit(). #include <fenv.h> //feclearexcept(), fetestexcept(). //-------------------------------------------------------------------------- // Application-specific datatypes. //-------------------------------------------------------------------------- // 8-bit unsigned integer. typedef unsigned char byte_t; // Indices in an array of RGB values. enum colorcoord_t { CC_R = 0, CC_G, CC_B }; // RGB pixel value, each elem unsigned 8-bit. struct byte3_t { byte_t a[3]; byte3_t( void ) { a[0] = 0; a[1] = 0; a[2] = 0; } }; // RGB pixel value, each elem double in [0,1]. struct double3_t { double a[3]; double3_t( void ) { a[0] = 0.0; a[1] = 0.0; a[2] = 0.0; } }; //-------------------------------------------------------------------------- // Scanning text from input file. //-------------------------------------------------------------------------- bool char_isWS( char iC ) { return ( (iC==' ') || (iC=='\t') || (iC=='\n') ); } int skipWSAndComment( FILE * iF ) { int c; while ( c = fgetc(iF), c != EOF ) { if ( char_isWS( c ) ) { /*empty*/ } else if ( c == '#' ) //Comment line { while ( c = fgetc(iF), c != EOF && c != '\n' ) { ; } } else { break; //LEAVE LOOP. } } return c; } bool scanDecInt( FILE * iF, int * uC, //in & out int * oVal ) { bool seenOne = 0; int val = 0; while ( BETWEEN_II( '0', *uC , '9' ) ) { seenOne = 1; val *= 10; val += *uC - '0'; *uC = fgetc(iF); } *oVal = val; return seenOne; } //-------------------------------------------------------------------------- // Reading and writing pixel values from/to PPM "P6" file. //-------------------------------------------------------------------------- bool getPixel_byte3( FILE * iF, byte3_t * oPixelByte ) { if ( fread( oPixelByte, 3, 1, iF ) != 1 ) { return false; } return true; } bool putPixel_byte3( FILE * oF, byte3_t const * iPixelByte ) { if ( fwrite( iPixelByte, 3, 1, oF ) != 1 ) { return false; } return true; } double byteToDouble( byte_t i ) { return (double)i / 255.0; } byte_t doubleToByte( double i ) { return (byte_t)( (255.0 * i) + 0.49999 ); } void byte3ToDouble3( byte3_t const * in, double3_t * out ) { for ( int k = 0; k < 3; k++ ) { out->a[k] = byteToDouble( in->a[k] ); } } void double3ToByte3( double3_t const * in, byte3_t * out ) { for ( int k = 0; k < 3; k++ ) { out->a[k] = doubleToByte( in->a[k] ); } } bool getPixel_double3( FILE * iF, double3_t * oPixelDouble ) { byte3_t pixelByte; if ( ! getPixel_byte3( iF, &pixelByte ) ) { return false; } byte3ToDouble3( &pixelByte, oPixelDouble ); return true; } bool putPixel_double3( FILE * oF, double3_t const * iPixelDouble ) { byte3_t pixelByte; double3ToByte3( iPixelDouble, &pixelByte ); return putPixel_byte3( oF, &pixelByte ); } //-------------------------------------------------------------------------- // Program abortion on error // (crude but suffices for a small program like this). //-------------------------------------------------------------------------- #include <stdarg.h> void fatal( const char * iFmt, ... ) { fprintf( stderr, "fltrppm4: fatal: " ); va_list ap; va_start( ap, iFmt ); vfprintf( stderr, iFmt, ap ); va_end( ap ); fprintf( stderr, "\n" ); exit( -1 ); } //-------------------------------------------------------------------------- // Filter one pixel. //-------------------------------------------------------------------------- // Clip a double to the interval [0.0, 1.0]. double clip( double iVal ) { if ( iVal < 0.0 ) { return 0.0; } if ( iVal > 1.0 ) { return 1.0; } return iVal; } void filterPixel( RPInterpreter * uRP, int iX, //} Pixel coordinates (for diagnostic message). int iY, //} double3_t const * iPixel, double3_t * oPixel, bool iChkEvery ) //Check for domain error at every pixel. { // Set variables. uRP->clearVars(); uRP->putVar( 'r', iPixel->a[CC_R] ); uRP->putVar( 'g', iPixel->a[CC_G] ); uRP->putVar( 'b', iPixel->a[CC_B] ); // Evaluate expression. uRP->evaluate(); // Get output variable values. double rOut; double gOut; double bOut; bool f = uRP->getVar( 'r', &rOut ) && uRP->getVar( 'g', &gOut ) && uRP->getVar( 'b', &bOut ); if ( iChkEvery && !f ) { fatal( "Pixel x=%d y=%d: Runtime DomainError\n", iX, iY ); } // Set output RGB values. oPixel->a[CC_R] = clip( rOut ); oPixel->a[CC_G] = clip( gOut ); oPixel->a[CC_B] = clip( bOut ); } //-------------------------------------------------------------------------- // Filter PPM file. // Input = stdio FILE. // output = stdio FILE. // On error, call 'fatal()' to abort the program. //-------------------------------------------------------------------------- // Parameters: // uRP = RPInterpreter object, containing the parsed expression. // iF = Input file. // oF = Output file. void filterPPM( RPInterpreter * uRP, FILE * iF, FILE * oF, bool iChkEvery ) //Check for domain error at every pixel. { // // Read/write the PPM file prologue. // int c1; int c2; if ( c1 = fgetc( iF ), c1 == EOF ) { fatal( "fgetc c1" ); } if ( c2 = fgetc( iF ), c2 == EOF ) { fatal( "fgetc c1" ); } if ( ! ( c1 == 'P' && c2 == '6' ) ) { fatal( "mnum != 'P6'" ); } int c; int wid; int hih; int maxclr; c = skipWSAndComment( iF ); if ( c == EOF ) { fatal( "EOF before width" ); } if ( ! scanDecInt( iF, &c, &wid ) ) { fatal( "error scanning width" ); } c = skipWSAndComment( iF ); if ( c == EOF ) { fatal( "EOF before height" ); } if ( ! scanDecInt( iF, &c, &hih ) ) { fatal( "error scanning height" ); } c = skipWSAndComment( iF ); if ( c == EOF ) { fatal( "EOF before max-color" ); } if ( ! scanDecInt( iF, &c, &maxclr ) ) { fatal( "error scanning maxcolor" ); } fprintf( oF, "P6 %u %u %u\n", wid, hih, maxclr ); if ( maxclr != 255 ) { fatal( "This program handles only PPM files with maxcolor == 255" ); } // // Filter the pixels. // for ( int y = 0; y < hih; y++ ) { for ( int x = 0; x < wid; x++ ) { double3_t pixelIn; if ( ! getPixel_double3( iF, &pixelIn ) ) { fatal( "Missing pixel data in file" ); } double3_t pixelOut; filterPixel( uRP, x, y, &pixelIn, &pixelOut, iChkEvery ); putPixel_double3( oF, &pixelOut ); }//x }//y } //-------------------------------------------------------------------------- // MAIN. //-------------------------------------------------------------------------- #include <string.h> //strcmp(). void usage() { fprintf( stderr, "Usage: fltrppm4 'EXPRESSION' [options]\n" "Options: \n" " -v Verbose.\n" " -d Check for domain error at EVERY pixel.\n" "\n" "Note: Enclose EXPRESSION in single quotes to prevent shell expansion.\n" "\n" ); fprintf( stderr, "%s\n", RPInterpreter::helptxt() ); } int main( int argc, char ** argv ) { // // Cmd line args. // if ( argc < 2 ) { usage(); return -1; } char const * arg_expr = argv[1]; bool arg_v = false; bool arg_chkEvery = false; for ( int iOpt = 2; iOpt < argc; iOpt++ ) { if ( strcmp( argv[iOpt], "-v" ) == 0 ) { arg_v = true; } else if ( strcmp( argv[iOpt], "-d" ) == 0 ) { arg_chkEvery = true; } else { usage(); return -1; } } // // Create RPInterpreter object, // scan the expression string. // RPInterpreter rp; if ( ! rp.scanFromString( arg_expr ) ) { return -1; } if ( arg_v ) { rp.dump( stderr ); } // // Filter the PPM file. // feclearexcept( FE_ALL_EXCEPT ); filterPPM( &rp, stdin, stdout, arg_chkEvery ); if ( fetestexcept( FE_INVALID ) ) { fatal( "Runtime DomainError\n" ); } return 0; }