1 /**
2  * @brief Display Adaptive TMO
3  *
4  * From:
5  * Rafal Mantiuk, Scott Daly, Louis Kerofsky.
6  * Display Adaptive Tone Mapping.
7  * To appear in: ACM Transactions on Graphics (Proc. of SIGGRAPH'08) 27 (3)
8  * http://www.mpi-inf.mpg.de/resources/hdr/datmo/
9  *
10  * This file is a part of PFSTMO package.
11  * ----------------------------------------------------------------------
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25  * ----------------------------------------------------------------------
26  *
27  * @author Rafal Mantiuk, <mantiuk@gmail.com>
28  *
29  * $Id: pfstmo_mantiuk08.cpp,v 1.19 2013/12/28 14:00:54 rafm Exp $
30  */
31 
32 #include <config.h>
33 
34 #include <iostream>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <math.h>
38 #include <getopt.h>
39 #include <fcntl.h>
40 #include <iostream>
41 #include <memory>
42 
43 #include <sys/time.h>
44 
45 #include <pfs.h>
46 
47 #include "display_adaptive_tmo.h"
48 
49 
50 #define PROG_NAME "pfstmo_mantiuk08"
51 
52 class QuietException
53 {
54 };
55 
56 
57 class Timing
58 {
59   timeval t1;
60 public:
Timing()61   Timing() {
62     gettimeofday(&t1, NULL);
63   }
64 
report(const char * activity)65   void report( const char *activity )
66   {
67     timeval t2;
68     gettimeofday(&t2, NULL);
69     unsigned int t = (t2.tv_sec - t1.tv_sec) * 1000000 + (t2.tv_usec - t1.tv_usec);
70 
71     fprintf( stderr, "Activity %s took %g seconds.\n", activity,  (float)t / 1000000.f );
72   }
73 };
74 
75 using namespace std;
76 
77 const char *temp_file_2pass = "datmo_tone_curves.tmp";
78 bool read_tone_curve( FILE *fh, datmoToneCurve *tc, double **x_scale );
79 
printHelp()80 void printHelp()
81 {
82   fprintf( stderr, PROG_NAME " (" PACKAGE_STRING ") : \n"
83     "\t[--display-function <df-spec>] [--display-size=<size-spec>]\n"
84     "\t[--color-saturation <float>] [--contrast-enhancement <float>]\n"
85     "\t[--white-y=<float>]\n"
86     "\t[--verbose] [--quiet] [--help]\n"
87     "See man page for more information.\n" );
88 }
89 
90 
91 bool verbose = false;
92 bool quiet = false;
93 
94 #define FRAME_NAME_MAX 30
95 char frame_name[FRAME_NAME_MAX+1];
96 
progress_report(int progress)97 int progress_report( int progress )
98 {
99   if( !quiet ) {
100     fprintf( stderr, "\r'%s' completed %d%%", frame_name, progress );
101     if( progress == 100 )
102       fprintf( stderr, "\n" );
103   }
104   return PFSTMO_CB_CONTINUE;
105 }
106 
107 
108 
tmo_mantiuk08(int argc,char * argv[])109 void tmo_mantiuk08(int argc, char * argv[])
110 {
111 
112   //--- default tone mapping parameters;
113   float contrast_enhance_factor = 1.f;
114   float saturation_factor = 1.f;
115   float white_y = -2.f;
116   float fps = 25;
117   int temporal_filter = 0;
118   int itmax = 200;
119   float tol = 1e-3;
120   DisplayFunction *df = NULL;
121   DisplaySize *ds = NULL;
122   const char *output_tc = NULL;
123   datmoVisualModel visual_model = vm_full;
124   double scene_l_adapt = 1000;
125 
126   //--- process command line args
127 
128   df = createDisplayFunctionFromArgs( argc, argv );
129   if( df == NULL )
130     df = new DisplayFunctionGGBA( "lcd" );
131 
132   ds = createDisplaySizeFromArgs( argc, argv );
133   if( ds == NULL )
134     ds = new DisplaySize( 30.f, 0.5f );
135 
136   static struct option cmdLineOptions[] = {
137     { "help", no_argument, NULL, 'h' },
138     { "verbose", no_argument, NULL, 'v' },
139     { "contrast-enhancement", required_argument, NULL, 'e' },
140     { "color-saturation", required_argument, NULL, 'c' },
141     { "white-y", required_argument, NULL, 'y' },
142     { "fps", required_argument, NULL, 'f' },
143     { "output-tone-curve", required_argument, NULL, 'o' },
144     { "visual-model", required_argument, NULL, 'm' },
145     { "scene-y-adapt", required_argument, NULL, 'a' },
146     { "quiet", no_argument, NULL, 'q' },
147     { NULL, 0, NULL, 0 }
148   };
149 
150   int optionIndex = 0;
151   while( 1 ) {
152     int c = getopt_long (argc, argv, "vhe:c:y:t:o:qm:f:a:", cmdLineOptions, &optionIndex);
153     if( c == -1 ) break;
154     switch( c ) {
155     case 'h':
156       printHelp();
157       throw QuietException();
158     case 'v':
159       verbose = true;
160       break;
161     case 'q':
162       quiet = true;
163       break;
164     case 'a':
165       if( !strcmp( optarg, "auto" ) )
166         scene_l_adapt = -1;
167       else {
168         scene_l_adapt = (float)strtod( optarg, NULL );
169         if( scene_l_adapt <= 0.0f )
170         throw pfs::Exception("incorrect scane adaptation luminance. The value must be greater than 0.");
171       }
172       break;
173     case 'e':
174       contrast_enhance_factor = (float)strtod( optarg, NULL );
175       if( contrast_enhance_factor <= 0.0f )
176         throw pfs::Exception("incorrect contrast enhancement factor, accepted value must be a positive number");
177       break;
178     case 'c':
179       saturation_factor = (float)strtod( optarg, NULL );
180       if( saturation_factor < 0.0f || saturation_factor > 2.0f )
181         throw pfs::Exception("incorrect saturation factor, accepted range is (0..2)");
182       break;
183     case 'm':
184     {
185       char *saveptr;
186       char *token;
187       visual_model = vm_none;
188 
189       token = strtok_r( optarg, ",:", &saveptr );
190       while( token != NULL ) {
191 
192         if( !strcmp( token, "none" ) ) {
193           visual_model = vm_none;
194         } else if( !strcmp( token, "full" ) ) {
195           visual_model = vm_full;
196         } else if( !strcmp( token, "luminance_masking" ) ) {
197           visual_model |= vm_luminance_masking;
198         } else if( !strcmp( token, "contrast_masking" ) ) {
199           visual_model |= vm_contrast_masking;
200         } else if( !strcmp( token, "csf" ) ) {
201           visual_model |= vm_csf;
202         } else
203           throw pfs::Exception("Unrecognized visual model");
204 
205         token = strtok_r( NULL, ",:", &saveptr );
206       }
207     }
208 
209       break;
210 
211     case 'y':
212       if( !strcmp( optarg, "none" ) )
213         white_y = -1;
214       else
215         white_y = (float)strtod( optarg, NULL );
216       if( white_y < 0.0f )
217         throw pfs::Exception("incorrect white-y value. The value must be greater than 0");
218       break;
219     case 'f':
220       fps = strtod( optarg, NULL );
221       if( fps != 25 && fps != 30 && fps != 60 )
222         throw pfs::Exception("Only 3 frame-per-seconds values are supported: 25, 30 and 60.");
223       break;
224     case 'o':
225       output_tc = optarg;
226       break;
227     case '?':
228       throw QuietException();
229     case ':':
230       throw QuietException();
231     }
232   }
233 
234   if( verbose ) {
235     df->print( stderr );
236     ds->print( stderr );
237     fprintf( stderr, "Frames-per-second: %g\n", fps );
238     fprintf( stderr, "Contrast masking: %d\n", (bool)(visual_model & vm_contrast_masking) );
239     fprintf( stderr, "Luminance masking: %d\n", (bool)(visual_model & vm_luminance_masking) );
240     fprintf( stderr, "CSF: %d\n", (bool)(visual_model & vm_csf) );
241     fprintf( stderr, "Scane adaptation luminance: %g (-1 means auto)\n", scene_l_adapt );
242   }
243 
244   Timing tm_entire;
245 
246   FILE *tc_FH = NULL;
247   if( output_tc != NULL ) {
248     tc_FH = fopen( output_tc, "w" );
249     if( tc_FH == NULL )
250       throw pfs::Exception("cannot open file for writing tone-curve.");
251   }
252 
253 
254   datmoTCFilter rc_filter( fps, log10(df->display(0)), log10(df->display(1)) );
255   pfs::DOMIO pfsio;
256 
257   size_t frame_no = 0;
258   while( true ) {
259     pfs::Frame *frame = pfsio.readFrame( stdin );
260     if( frame == NULL )
261       break;
262 
263     pfs::Channel *inX, *inY, *inZ;
264 
265     frame->getXYZChannels(inX, inY, inZ);
266     int cols = frame->getWidth();
267     int rows = frame->getHeight();
268 
269     const char *file_name = frame->getTags()->getString( "FILE_NAME" );
270     if( file_name == NULL )
271       sprintf( frame_name, "frame #%d", (int)frame_no );
272     else {
273       int len = strlen( file_name );
274       if( len > FRAME_NAME_MAX ) // In case file name is too long
275         len = FRAME_NAME_MAX-3;
276       strcpy( frame_name, "..." );
277       strncpy( frame_name+3, file_name + strlen( file_name ) - len, len+1 );
278     }
279 
280 
281     {
282       pfs::Array2DImpl R( cols, rows );
283 
284       pfs::transformColorSpace( pfs::CS_XYZ, inX, inY, inZ, pfs::CS_RGB, inX, &R, inZ );
285 
286       if( white_y == -2.f ) {       // If not overriden by command line options
287         const char *white_y_str = frame->getTags()->getString( "WHITE_Y" );
288         if( white_y_str != NULL ) {
289           white_y = strtof( white_y_str, NULL );
290           if( white_y == 0 ) {
291             white_y = -1;
292             fprintf( stderr, PROG_NAME ": warning - wrong WHITE_Y in the input image" );
293           }
294         }
295       }
296       if( verbose && frame_no == 0 ) {
297         fprintf( stderr, "Luminance factor of the reference white: " );
298         if( white_y < 0 )
299           fprintf( stderr, "not specified\n" );
300         else
301           fprintf( stderr, "%g\n", white_y );
302       }
303 
304       const char *lum_data = frame->getTags()->getString("LUMINANCE");
305       if( lum_data != NULL && !strcmp( lum_data, "DISPLAY" ) && frame_no == 0 )
306         fprintf( stderr, PROG_NAME " warning: input image should be in linear (not gamma corrected) luminance factor units. Use '--linear' option with pfsin* commands.\n" );
307 
308       Timing tm_cond_dens;
309         std::auto_ptr<datmoConditionalDensity> C = datmo_compute_conditional_density( cols, rows, inY->getRawData(), progress_report );
310         if( C.get() == NULL )
311           throw pfs::Exception("failed to analyse the image");
312 	tm_cond_dens.report( "Conditional density" );
313 
314 	Timing tm_comp_tone_curve;
315         int res;
316         datmoToneCurve *tc = rc_filter.getToneCurvePtr();
317         res = datmo_compute_tone_curve( tc, C.get(), df, ds, contrast_enhance_factor, white_y, progress_report, visual_model, scene_l_adapt );
318         if( res != PFSTMO_OK )
319           throw pfs::Exception( "failed to compute a tone-curve" );
320 
321         datmoToneCurve *tc_filt = rc_filter.filterToneCurve();
322 	tm_comp_tone_curve.report( "Computing a tone-cuve" );
323 
324 
325         {
326 
327 	  Timing tm_tonecurve;
328         int res;
329         res = datmo_apply_tone_curve_cc( inX->getRawData(), R.getRawData(), inZ->getRawData(), cols, rows,
330           inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(), tc_filt,
331           df, saturation_factor );
332         if( res != PFSTMO_OK )
333           throw pfs::Exception( "failed to tone-map an image" );
334 
335 	tm_tonecurve.report( "Apply tone-curve" );
336 
337         if( tc_FH != NULL ) {
338           for( int i=0; i < tc_filt->size; i++ )
339             fprintf( tc_FH, "%d,%g,%g,%g\n", (int)frame_no, tc_filt->x_i[i], tc_filt->y_i[i], df->inv_display( (float)pow( 10, tc_filt->y_i[i] ) ) );
340         }
341 
342 //    int res;
343 //    res = datmo_tonemap( inX->getRawData(), R.getRawData(), inZ->getRawData(), cols, rows,
344 //      inX->getRawData(), R.getRawData(), inZ->getRawData(), inY->getRawData(),
345 //    df, ds, contrast_enhance_factor, saturation_factor, white_y, progress_report );
346 
347         progress_report( 100 );
348 
349         pfs::transformColorSpace( pfs::CS_RGB, inX, &R, inZ, pfs::CS_XYZ, inX, inY, inZ );
350         frame->getTags()->setString("LUMINANCE", "DISPLAY");
351 
352         pfsio.writeFrame( frame, stdout );
353       }
354 
355       pfsio.freeFrame(frame);
356     }
357 
358 
359     frame_no++;
360 
361   }
362 
363 
364     if( tc_FH != NULL )
365       fclose( tc_FH );
366 
367   delete df;
368   delete ds;
369 
370   tm_entire.report( "Entire operation" );
371 }
372 
read_tone_curve(FILE * fh,datmoToneCurve * tc,double ** x_scale)373 bool read_tone_curve( FILE *fh, datmoToneCurve *tc, double **x_scale )
374 {
375   int size, frame_no, read;
376   read = fscanf( fh, "%d,%d\n", &frame_no, &size );
377   if( read != 2 )
378     return false;
379 
380   if( *x_scale == NULL )
381     *x_scale = new double[size];
382 
383   tc->init( size, *x_scale );
384 
385   for( int i=0; i < size; i++ ) {
386     float x, y;
387     read = fscanf( fh, "%f,%f\n", &x, &y );
388     if( read != 2 )
389       throw pfs::Exception( "missing data in the 2-pass file"  );
390     (*x_scale)[i] = x;
391     tc->y_i[i] = y;
392   }
393   return true;
394 }
395 
396 
397 
main(int argc,char * argv[])398 int main( int argc, char* argv[] )
399 {
400   try {
401     tmo_mantiuk08( argc, argv );
402   }
403   catch( pfs::Exception ex ) {
404     fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() );
405     return EXIT_FAILURE;
406   }
407   catch( QuietException  ex ) {
408     return EXIT_FAILURE;
409   }
410   return EXIT_SUCCESS;
411 }
412 
413