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