// 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;
}