1 /**
2  * @brief Create a web page with an HDR viewer
3  *
4  * This file is a part of PFSTOOLS package.
5  * ----------------------------------------------------------------------
6  * Copyright (C) 2009 Rafal Mantiuk
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  * ----------------------------------------------------------------------
22  *
23  * @author Rafal Mantiuk, <mantiuk@mpi-sb.mpg.de>
24  *
25  * $Id: hdrhtml.cpp,v 1.8 2014/06/16 21:50:08 rafm Exp $
26  */
27 
28 #include "hdrhtml.h"
29 
30 #include <limits>
31 #include <algorithm>
32 #include <math.h>
33 #include <fstream>
34 #include <sstream>
35 #include <iostream>
36 #include <limits>
37 
38 #include <pfs.h>
39 
40 #include <Magick++.h>
41 // This is to get rid of warnings due to double defines from Magick++
42 #undef PACKAGE
43 #undef PACKAGE_BUGREPORT
44 #undef PACKAGE_NAME
45 #undef PACKAGE_STRING
46 #undef PACKAGE_TARNAME
47 #undef PACKAGE_VERSION
48 #undef VERSION
49 
50 #include <config.h>
51 
52 using namespace std;
53 
54 // ================================================
55 //        Parameters controllig the web page
56 // ================================================
57 
58 const int f_step_res = 3; // How many steps per f-stop (do not change)
59 const int pix_per_fstop = 25;   // Distance in pixels between f-stops shown on the histogram
60 const char *hdrhtml_version = "1.0"; // Version of the HDRHTML code
61 
62 
63 // ================================================
64 //                Histogram
65 // ================================================
66 
67 template<class T>
68 class Histogram
69 {
70 public:
71   T *x;                         // Bin centers
72   size_t *n;                       // No of items in a bin
73   size_t bins;
74 
Histogram()75   Histogram() : x( NULL ), n( NULL )
76   {
77   }
78 
~Histogram()79   ~Histogram()
80   {
81     free();
82   }
83 
free()84   void free()
85   {
86     delete []x;
87     delete []n;
88     bins = 0;
89   }
90 
compute(const T * data,size_t d_size,int bins=30,T min_val=1,T max_val=-1,bool reject_outofrange=true)91   void compute( const T *data, size_t d_size, int bins = 30, T min_val = 1, T max_val = -1, bool reject_outofrange = true )
92   {
93     assert( bins > 0 );
94 
95     free();
96     this->bins = bins;
97 
98     if( min_val > max_val )             // missing min/max info
99     {
100       min_val = numeric_limits<T>::max();
101       max_val = numeric_limits<T>::min();
102 
103       for( int k=0; k < d_size; k++ ) {
104         if( data[k] > max_val ) max_val = data[k];
105         if( data[k] < min_val ) min_val = data[k];
106       }
107     }
108 
109     x = new T[bins];
110     n = new size_t[bins];
111 
112     T delta = (max_val-min_val) / (float)bins; // width of a single bin
113 
114 //     T *e = new T[bins+1];        // bin edges
115 //     for( int k=0; k <= bins; k++ ) {
116 //       e[k] = min_val + (float)k * delta;
117 //     }
118     for( int k=0; k < bins; k++ ) {
119       x[k] = min_val + (float)k * delta + delta/2;
120       n[k] = 0;
121     }
122 
123     if( reject_outofrange ) {
124       for( int k=0; k < d_size; k++ ) {
125         int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins );
126         if( ind < 0 )
127           continue;
128         if( ind >= bins )
129           continue;
130         n[ind]++;
131       }
132     } else {
133       for( int k=0; k < d_size; k++ ) {
134         int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins );
135         if( ind < 0 ) {
136           n[0]++;
137           continue;
138         }
139         if( ind >= bins ) {
140           n[bins-1]++;
141           continue;
142         }
143         n[ind]++;
144       }
145     }
146 
147 
148 
149   }
150 
151 };
152 
153 // ================================================
154 //                  Lookup table
155 // ================================================
156 
157 /**
158  * Lookup table on a uniform array & interpolation
159  *
160  * x_i must be at least two elements
161  * y_i must be initialized after creating an object
162  */
163 class UniformArrayLUT
164 {
165   const float *x_i;
166   size_t lut_size;
167   float delta;
168 
169   bool own_y_i;
170 public:
171   float *y_i;
172 
UniformArrayLUT(size_t lut_size,const float * x_i,float * y_i=NULL)173   UniformArrayLUT( size_t lut_size, const float *x_i, float *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] )
174   {
175     if( y_i == NULL ) {
176       this->y_i = new float[lut_size];
177       own_y_i = true;
178     } else {
179       this->y_i = y_i;
180       own_y_i = false;
181     }
182   }
183 
UniformArrayLUT()184   UniformArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ), delta( 0. ) {}
185 
UniformArrayLUT(const UniformArrayLUT & other)186   UniformArrayLUT(const UniformArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta )
187   {
188     this->y_i = new float[lut_size];
189     own_y_i = true;
190     memcpy(this->y_i, other.y_i, lut_size * sizeof(float));
191   }
192 
operator =(const UniformArrayLUT & other)193   UniformArrayLUT& operator = (const UniformArrayLUT& other)
194   {
195     this->lut_size = other.lut_size;
196     this->delta = other.delta;
197     this->x_i = other.x_i;
198     this->y_i = new float[lut_size];
199     own_y_i = true;
200     memcpy(this->y_i, other.y_i, lut_size * sizeof(float));
201     return *this;
202   }
203 
~UniformArrayLUT()204   ~UniformArrayLUT()
205   {
206     if( own_y_i )
207       delete []y_i;
208   }
209 
interp(float x)210   float interp( float x )
211   {
212     const float ind_f = (x - x_i[0])/delta;
213     const size_t ind_low = (size_t)(ind_f);
214     const size_t ind_hi = (size_t)ceil(ind_f);
215 
216     if( (ind_f < 0) )           // Out of range checks
217       return y_i[0];
218     if( (ind_hi >= lut_size) )
219       return y_i[lut_size-1];
220 
221     if( (ind_low == ind_hi) )
222       return y_i[ind_low];      // No interpolation necessary
223 
224     return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(float)ind_low); // Interpolation
225   }
226 
227 };
228 
229 template<class T>
clamp(T x,T min,T max)230 inline T clamp( T x, T min, T max )
231 {
232   if( x < min )
233     return min;
234   if( x > max )
235     return max;
236   return x;
237 }
238 
239 /**
240  * Lookup table on an arbitrary array
241  *
242  * x_i must be at least two elements
243  * y_i must be initialized after creating an object
244  */
245 template<class Tx, class Ty>
246 class ArrayLUT
247 {
248   const Tx *x_i;
249   size_t lut_size;
250 
251   bool own_y_i;
252 public:
253   Ty *y_i;
254 
ArrayLUT(size_t lut_size,const Tx * x_i,Ty * y_i=NULL)255   ArrayLUT( size_t lut_size, const Tx *x_i, Ty *y_i = NULL ) : x_i( x_i ), lut_size( lut_size )
256   {
257     assert( lut_size > 0 );
258 
259     if( y_i == NULL ) {
260       this->y_i = new Ty[lut_size];
261       own_y_i = true;
262     } else {
263       this->y_i = y_i;
264       own_y_i = false;
265     }
266   }
267 
ArrayLUT()268   ArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ) {}
269 
ArrayLUT(const ArrayLUT & other)270   ArrayLUT(const ArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size )
271   {
272     this->y_i = new Ty[lut_size];
273     own_y_i = true;
274     memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty));
275   }
276 
operator =(const ArrayLUT & other)277   ArrayLUT& operator = (const ArrayLUT& other)
278   {
279     this->lut_size = other.lut_size;
280     this->x_i = other.x_i;
281     this->y_i = new Ty[lut_size];
282     own_y_i = true;
283     memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty));
284   }
285 
~ArrayLUT()286   ~ArrayLUT()
287   {
288     if( own_y_i )
289       delete []y_i;
290   }
291 
interp(Tx x)292   Ty interp( Tx x )
293   {
294     if( (x <= x_i[0]) )           // Out of range checks
295       return y_i[0];
296     if( (x >= x_i[lut_size-1]) )
297       return y_i[lut_size-1];
298 
299     // binary search
300     size_t l = 0, r = lut_size-1;
301     while( true ) {
302       size_t m = (l+r)/2;
303       if( m == l ) break;
304       if( x < x_i[m] )
305         r = m;
306       else
307         l = m;
308     }
309 
310     float alpha = (x - x_i[l])/(x_i[r]-x_i[l]);
311 
312     return y_i[l] + (Ty)(alpha * (y_i[r]-y_i[l]));
313   }
314 
315 
316 };
317 
318 
319 // ================================================
320 //                  Percentiles
321 // ================================================
322 
323 
324 /**
325  * Compute prctiles using image cummulative histogram, which is less
326  * accurate method but much faster than sorting.
327  */
328 template<class T>
329 class Percentiles
330 {
331   Histogram<T> hist;
332   const size_t bin_n;
333   size_t d_size;
334 public:
335 
336   /**
337    * @param data - table with samples
338    * @param d_size - number of samples
339    */
Percentiles(const T * data,size_t d_size)340   Percentiles( const T *data, size_t d_size ) :
341     bin_n( 1000 ), d_size( d_size ) // Accuracy 0.1 prctile
342   {
343     hist.compute( data, d_size, bin_n, 1, -1, false );
344     // Compute cummulative histogram
345     for( int k = 1; k < bin_n; k++ )
346       hist.n[k] += hist.n[k-1];
347 
348 //    cerr << "d_size: " << d_size << "  hist.n: " << hist.n[bin_n-1] << "\n";
349     assert( hist.n[bin_n-1] == d_size );
350   }
351 
prctile(double p)352   T prctile( double p )
353   {
354     ArrayLUT<size_t,T> lut( hist.bins, hist.n, hist.x );
355 
356     return lut.interp( (size_t)(p*(double)d_size/100.) );
357   }
358 
359 
360 
361 };
362 
363 
364 // ================================================
365 //            Text template file utils
366 // ================================================
367 
368 typedef void (*replace_callback)( ostream &out, void *user_data, const char *parameter );
369 
370 class ReplacePattern
371 {
372 
373 public:
374 
375   const char* pattern;
376   std::string replace_with;
377   replace_callback callback;
378   void *user_data;
379 
ReplacePattern(const char * pattern,std::string replace_with)380   ReplacePattern( const char* pattern, std::string replace_with ) :
381     pattern( pattern ), replace_with( replace_with ), callback( NULL )
382   {
383   }
384 
ReplacePattern(const char * pattern,float replace_with_num)385   ReplacePattern( const char* pattern, float replace_with_num ) :
386     pattern( pattern ), callback( NULL )
387   {
388     std::ostringstream num_str;
389     num_str << replace_with_num;
390     replace_with = num_str.str();
391   }
392 
ReplacePattern(const char * pattern,int replace_with_num)393   ReplacePattern( const char* pattern, int replace_with_num ) :
394     pattern( pattern ), callback( NULL )
395   {
396     std::ostringstream num_str;
397     num_str << replace_with_num;
398     replace_with = num_str.str();
399   }
400 
ReplacePattern(const char * pattern,replace_callback callback,void * user_data=NULL)401   ReplacePattern( const char* pattern, replace_callback callback, void *user_data = NULL ) :
402     pattern( pattern ), callback( callback ), user_data( user_data )
403   {
404   }
405 
ReplacePattern()406   ReplacePattern() : pattern( NULL ), callback( NULL )
407   {
408   }
409 
write_replacement(ostream & out,const char * parameter=NULL)410   virtual void write_replacement( ostream &out, const char *parameter = NULL )
411   {
412     if( callback != NULL )
413       callback( out, user_data, parameter );
414     else
415       out << replace_with;
416   }
417 
418 
419 };
420 
421 
create_from_template(std::ostream & outfs,const char * template_file_name,ReplacePattern * pattern_list)422 void create_from_template( std::ostream &outfs, const char *template_file_name,
423   ReplacePattern *pattern_list )
424 {
425   std::ifstream infs( template_file_name );
426   if( !infs.good() ) {
427     std::ostringstream error_message;
428     error_message << "Cannot open '" << template_file_name << "' for reading";
429     throw pfs::Exception( error_message.str().c_str() );
430   }
431 
432 
433   const int MAX_LINE_LENGTH = 2048;
434 //  int lines = 0;
435   while( true ) {
436     char line[MAX_LINE_LENGTH];
437     infs.getline( line, MAX_LINE_LENGTH );
438 
439     if( !infs.good() )
440       break;
441 
442     std::string line_str( line );
443     int pos = 0;
444 
445     while( true ) {
446       int find_pos = line_str.find_first_of( '@', pos );
447       if( find_pos == std::string::npos ) {
448         outfs << line_str.substr( pos, std::string::npos );
449         break;
450       }
451 
452       bool replaced = false;
453       int end_marker = line_str.find_first_of( "@[", find_pos+1 );
454       if( end_marker != std::string::npos ) {
455 
456         for( int k = 0; pattern_list[k].pattern != NULL; k++ )
457         {
458           if( line_str.compare( find_pos+1, end_marker-find_pos-1, pattern_list[k].pattern ) == 0 ) {
459             outfs << line_str.substr( pos, find_pos-pos );
460 
461             std::string parameter;
462             if( line_str[end_marker] == '[' ) {
463               int param_endmarker = line_str.find_first_of( ']', end_marker+1 );
464               if( param_endmarker == std::string::npos )
465                 throw pfs::Exception( "Non-closed bracker in the replacement keyword" );
466               parameter = line_str.substr( end_marker+1, param_endmarker-end_marker-1 );
467               end_marker = param_endmarker+1;
468             }
469 
470             pattern_list[k].write_replacement( outfs, parameter.empty() ? NULL : parameter.c_str() );
471             pos = end_marker + 1;
472             replaced = true;
473             break;
474           }
475         }
476 
477       }
478       if( !replaced ) {
479         outfs << line_str.substr( pos, find_pos-pos+1 );
480         pos = find_pos+1;
481       }
482 
483     }
484 
485 
486     outfs << "\n";
487 
488   }
489 
490 }
491 
create_from_template(const char * output_file_name,const char * template_file_name,ReplacePattern * pattern_list)492 void create_from_template( const char *output_file_name, const char *template_file_name,
493   ReplacePattern *pattern_list )
494 {
495   std::ofstream outfs( output_file_name );
496   if( !outfs.good() ) {
497     std::ostringstream error_message;
498     error_message << "Cannot open '" << output_file_name << "' for writing";
499     throw pfs::Exception( error_message.str().c_str() );
500   }
501   create_from_template( outfs, template_file_name, pattern_list );
502 }
503 
504 
505 
506 
507 // ================================================
508 //            Read and parse CVS files
509 // ================================================
510 
511 
512 class CSVTable
513 {
514 public:
515 
516   float **data;
517   int columns, rows;
518 
CSVTable()519   CSVTable() : data( NULL )
520   {
521   }
522 
523 
~CSVTable()524   ~CSVTable()
525   {
526     free();
527   }
528 
free()529   void free()
530   {
531     if( data == NULL )
532       return;
533 
534     for( int k = 0; k < columns; k++ )
535       delete [] data[k];
536 
537     delete []data;
538 
539     data = NULL;
540   }
541 
542 
read(const char * file_name,int columns)543   void read( const char *file_name, int columns )
544   {
545     free();
546 
547     this->columns = columns;
548 
549     std::ifstream ifs( file_name );
550 
551     if( !ifs.is_open() ) {
552       std::string full_message( "Cannot open file: " );
553       full_message += file_name;
554       throw pfs::Exception( full_message.c_str() );
555     }
556 
557     std::list<float> value_list;
558 
559     const int MAX_LINE_LENGTH = 1024;
560     int lines = 0;
561     while( 1 ) {
562       char line[MAX_LINE_LENGTH];
563       ifs.getline( line, MAX_LINE_LENGTH );
564 
565       if( !ifs.good() )
566         break;
567 
568       std::string line_str( line );
569       int pos = 0;
570       for( int k=0; k < columns; k++ ) {
571         // Skip white spaces
572         while( line_str[pos] == ' ' || line_str[pos] == '\t' ) pos++;
573         int new_pos = line_str.find_first_of( ',', pos );
574         size_t len;
575         if( new_pos == std::string::npos ) {
576           if( k != columns-1 ) {
577             std::string full_message( "Missing column data in the file: " );
578             full_message += file_name;
579             throw pfs::Exception( full_message.c_str() );
580           }
581           len = std::string::npos;
582         } else
583           len = new_pos-pos;
584 
585         float value;
586         if( len == 0 ) {
587           value = numeric_limits<float>::quiet_NaN();
588         } else {
589           std::string token = line_str.substr( pos, len );
590           const char *str_beg = token.c_str();
591           char *str_end;
592 //          cerr << "token: " << str_beg << "\n";
593           value = strtof( str_beg, &str_end );
594           if( str_beg == str_end ) {
595             std::ostringstream error_message;
596             error_message << "Error parsing line " << lines+1 << " of " << file_name << "\n";
597             throw pfs::Exception( error_message.str().c_str() );
598           }
599         }
600 
601 
602         value_list.push_back( value );
603 
604         pos = new_pos+1;
605       }
606 
607       lines++;
608     }
609 
610     float **table = new float*[columns];
611     for( int c=0; c < columns; c++ )
612       table[c] = new float[lines];
613 
614     for( int l=0; l < lines; l++ )
615       for( int c=0; c < columns; c++ ) {
616         table[c][l] = value_list.front();
617         value_list.pop_front();
618       }
619 
620     data = table;
621     this->rows = lines;
622   }
623 
624 };
625 
626 
627 // ================================================
628 //                 HDR HTML code
629 // ================================================
630 
631 
add_image(int width,int height,float * R,float * G,float * B,float * Y,const char * base_name,int quality)632 void HDRHTMLSet::add_image( int width, int height, float *R, float *G, float *B,
633   float *Y,
634   const char *base_name, int quality )
635 {
636 
637   const int pixels = width*height;
638   const int basis_no = quality;
639 
640   // Load LUT for the basis tone-curves
641   std::ostringstream lut_filename;
642   lut_filename << PKG_DATADIR "/hdrhtml_t_b" << basis_no+1 << ".csv";
643   CSVTable basis_table;
644   basis_table.read( lut_filename.str().c_str(), basis_no+1 );
645   // Transform the first row (luminance factors) to the log domain
646   for( int k = 0; k < basis_table.rows; k++ )
647     basis_table.data[0][k] = log2f( basis_table.data[0][k] );
648 
649 // Fix zero and negative values in the image, convert to log2 space, find min and max values
650   float img_min = numeric_limits<float>::max();
651   float img_max = numeric_limits<float>::min();
652   {
653     float *arrays[] = { R, G, B, Y };
654     int k;
655 
656     for( k = 0; k < 4; k++ ) {
657       float *x = arrays[k];
658       float min_val = numeric_limits<float>::max(), max_val = numeric_limits<float>::min();
659       for( int i=0; i < pixels; i++ ) {
660         if( x[i] < min_val && x[i] > 0)
661           min_val = x[i];
662         if( x[i] > max_val )
663           max_val = x[i];
664       }
665       img_max = std::max( img_max, log2f(max_val) );
666       img_min = std::min( img_min, log2f(min_val) );
667 
668       for( int i=0; i < pixels; i++ ) {
669         if( x[i] < min_val )
670           x[i] = log2f(min_val);
671         else
672           x[i] = log2f(x[i]);
673       }
674     }
675   }
676 
677 
678   Percentiles<float> prc( Y, pixels );
679   img_min = prc.prctile( 0.1 );
680   img_max = prc.prctile( 99.9 );
681 
682   img_min -= 4;  // give extra room for brightenning
683   // how many 8-fstop segments we need to cover the DR
684   int f8_stops = ceil((img_max-img_min)/8);
685 
686   // start with this f-stop
687   float l_start = img_min + (img_max-img_min-f8_stops*8)/2;
688 
689   float l_med = prc.prctile( 50 );
690   float best_exp = round(l_med-l_start-4);
691 
692 // pix_per_fstop = 25;
693 
694 // % generate image histogram
695 
696   const int hist_height = 36;
697   int hist_width = width;
698 //  float hist_img_sz = [36 size(img,2)];
699   float hist_fstops = hist_width / pix_per_fstop;
700   float hist_start = (img_max-img_min-hist_fstops)/2;
701   {
702 
703     Histogram<float> hist;
704     hist.compute( Y, pixels, hist_width, img_min+hist_start, img_min+hist_start+hist_fstops );
705 
706     unsigned short *hist_buffer = new unsigned short[hist_width*hist_height*3];
707     float hist_n_max = -1;
708     for( int k = 0; k < hist_width; k++ )
709       hist_n_max = std::max( hist_n_max, (float)hist.n[k] );
710 
711     for( int k = 0; k < hist_width; k++ ) {
712       float top = hist_height - round((float)hist.n[k]/hist_n_max * hist_height);
713       for( int r = 0; r < hist_height; r++ ) {
714         hist_buffer[(r*hist_width+k)*3+0] = 0;
715         hist_buffer[(r*hist_width+k)*3+1] = (r>=top ? (1<<16) -1 : 0);
716         hist_buffer[(r*hist_width+k)*3+2] = 0;
717       }
718     }
719 
720 
721 // tick_fstops = (floor(hist_l(end))-ceil(hist_l(1)));
722 // ticks = round((ceil(hist_l(1))-hist_l(1))*pix_per_fstop) + (1:tick_fstops)*pix_per_fstop;
723 // hist_img(1:5,ticks,1:2) = 0.5;
724 // hist_img(end-4:end,ticks,1:2) = 0.5;
725 // plot_name = sprintf( '%s_hist.png', base_name );
726 // imwrite( hist_img, plot_name );
727 
728     Magick::Image hist_image( hist_width, hist_height,
729       "RGB", Magick::ShortPixel, hist_buffer );
730 
731     std::ostringstream img_filename;
732     if( image_dir != NULL )
733       img_filename << image_dir << "/";
734     img_filename << base_name << "_hist.png";
735     std::cerr << "Writing: " << img_filename.str() << "\n";
736     hist_image.write( img_filename.str().c_str() );
737 
738     delete []hist_buffer;
739   }
740 
741   // generate basis images
742 
743   unsigned short *imgBuffer =
744     new unsigned short[pixels*3];
745   for( int k=1; k <= f8_stops+1; k++ ) {
746 
747 
748     float max_value = (float)numeric_limits<unsigned short>::max(); //(1<<16) -1;
749 
750     float exp_multip = log2f(1/powf( 2, l_start + k*8 ));
751 
752     int max_basis = basis_no;
753     if( k == f8_stops+1 )     // Do only one shared basis for the last 8-fstop segment
754       max_basis = 1;
755 
756     for( int b=0; b < max_basis; b++ ) {
757       UniformArrayLUT basis_lut( basis_table.rows, basis_table.data[0], basis_table.data[b+1] );
758 
759       int i = 0;
760       for( int pix = 0; pix < pixels; pix++ ) {
761 
762         float rgb[3];
763         rgb[0] = R[pix];
764         rgb[1] = G[pix];
765         rgb[2] = B[pix];
766 
767         for( int c=0; c < 3; c++ ) {
768           float exposure_comp_v = rgb[c] + exp_multip;
769           float v = (basis_lut.interp(exposure_comp_v)*max_value);
770           imgBuffer[i++] = (unsigned short)(basis_lut.interp(exposure_comp_v)*max_value);
771         }
772       }
773       Magick::Image imImage( width, height,
774         "RGB", Magick::ShortPixel, imgBuffer );
775 
776       std::ostringstream img_filename;
777       if( image_dir != NULL )
778         img_filename << image_dir << "/";
779       img_filename << base_name << '_' << k-1 << '_' << b+1 << ".jpg";
780       std::cerr << "Writing: " << img_filename.str() << "\n";
781       imImage.write( img_filename.str().c_str() );
782     }
783 
784   }
785   delete [] imgBuffer;
786 
787   HDRHTMLImage new_image( base_name, width, height );
788 
789   new_image.hist_width = hist_width;
790   new_image.f8_stops = f8_stops;
791   new_image.f_step_res = f_step_res;
792   new_image.basis = basis_no;
793   new_image.shared_basis = 1;
794   new_image.pix_per_fstop = pix_per_fstop;
795   new_image.hist_start = hist_start;
796   new_image.hist_width = hist_width;
797   new_image.best_exp = best_exp;
798 
799   image_list.push_back( new_image );
800 
801 }
802 
803 void print_image_objects( ostream &out, void *user_data, const char *parameter );
804 void print_cf_table( ostream &out, void *user_data, const char *parameter );
805 void print_image_htmlcode( ostream &out, void *user_data, const char *parameter );
806 
generate_webpage(const char * page_template,const char * image_template,const char * object_output,const char * html_output)807 void HDRHTMLSet::generate_webpage( const char *page_template, const char *image_template,
808   const char *object_output, const char *html_output)
809 {
810   if( image_list.empty() )
811     return;
812 
813   std::ostringstream out_file_name;
814   if( page_name == NULL )
815     out_file_name << image_list.front().base_name << ".html";
816   else
817     out_file_name << page_name;
818 
819   // Load the table of the opacity coeffcients
820   std::ostringstream lut_filename;
821   lut_filename << PKG_DATADIR "/hdrhtml_c_b" << image_list.front().basis+1 << ".csv";
822   CSVTable coeff_table;
823   coeff_table.read( lut_filename.str().c_str(), image_list.front().basis+1 );
824 
825   ReplacePattern replace_list[] = {
826     ReplacePattern( "cf_array_def", print_cf_table, &coeff_table ),
827     ReplacePattern( "hdr_img_def", print_image_objects, this ),
828     ReplacePattern( "image_htmlcode", print_image_htmlcode, this ),
829     ReplacePattern( "title", page_name == NULL ? "HDRHTML viewer" : page_name ),
830     ReplacePattern( "version", hdrhtml_version ),
831     ReplacePattern()
832   };
833 
834   this->image_template = image_template;
835   create_from_template( out_file_name.str().c_str(), page_template, replace_list );
836 
837   if( object_output != NULL ) {
838     std::ofstream oofs( object_output );
839     if( !oofs.good() ) {
840       std::ostringstream error_message;
841       error_message << "Cannot open '" << object_output << "' for writing";
842       throw pfs::Exception( error_message.str().c_str() );
843     }
844     print_image_objects( oofs, this, NULL );
845   }
846 
847   if( html_output != NULL ) {
848     std::ofstream hofs( html_output );
849     if( !hofs.good() ) {
850       std::ostringstream error_message;
851       error_message << "Cannot open '" << html_output << "' for writing";
852       throw pfs::Exception( error_message.str().c_str() );
853     }
854     print_image_htmlcode( hofs, this, NULL );
855   }
856 
857 }
858 
print_image_objects(ostream & out,void * user_data,const char * parameter)859 void print_image_objects( ostream &out, void *user_data, const char *parameter )
860 {
861   HDRHTMLSet *hdrhtml_set = (HDRHTMLSet*)user_data;
862 
863   list<HDRHTMLImage>::iterator it;
864   for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) {
865     std::string obj_name( "hdr_" );
866     obj_name.append( it->base_name );
867 
868     out << obj_name << " = new Object();\n";
869     out << obj_name << ".width = " << it->width << ";\n";
870     out << obj_name << ".height = " << it->height << ";\n";
871     out << obj_name << ".f8_stops = " << it->f8_stops << ";\n";
872     out << obj_name << ".f_step_res = " << it->f_step_res << ";\n";
873     out << obj_name << ".base_name = \"" << it->base_name << "\";\n";
874     if( hdrhtml_set->image_dir==NULL )
875       out << obj_name << ".image_dir = \"\";\n";
876     else
877       out << obj_name << ".image_dir = \"" << hdrhtml_set->image_dir << "/\";\n";
878     out << obj_name << ".basis = " << it->basis << ";\n";
879     out << obj_name << ".shared_basis = " << it->shared_basis << ";\n";
880     out << obj_name << ".pix_per_fstop = " << it->pix_per_fstop << ";\n";
881     out << obj_name << ".hist_start = " << it->hist_start << ";\n";
882     out << obj_name << ".hist_width = " << it->hist_width << ";\n";
883     out << obj_name << ".exposure = " << it->best_exp << ";\n";
884     out << obj_name << ".best_exp = " << it->best_exp << ";\n\n";
885   }
886 
887 }
888 
print_image_htmlcode(ostream & out,HDRHTMLSet * hdrhtml_set,const HDRHTMLImage & it)889 void print_image_htmlcode( ostream &out, HDRHTMLSet *hdrhtml_set, const HDRHTMLImage &it )
890 {
891     std::string obj_name( "hdr_" );
892     obj_name.append( it.base_name );
893 
894     std::ostringstream img_dir;
895     if( hdrhtml_set->image_dir != NULL )
896       img_dir << hdrhtml_set->image_dir << "/";
897 
898     ReplacePattern replace_list[] = {
899       ReplacePattern( "hdr_img_width", it.width ),
900       ReplacePattern( "hdr_img_height", it.height ),
901       ReplacePattern( "img_dir", img_dir.str() ),
902       ReplacePattern( "hist_width", it.hist_width ),
903       ReplacePattern( "base_name", it.base_name ),
904       ReplacePattern( "help_mark_pos", it.hist_width-12 ),
905       ReplacePattern( "hdr_img_object", obj_name ),
906       ReplacePattern( "version", hdrhtml_version ),
907       ReplacePattern()
908     };
909 
910     create_from_template( out, hdrhtml_set->image_template, replace_list );
911 
912 }
913 
print_image_htmlcode(ostream & out,void * user_data,const char * parameter)914 void print_image_htmlcode( ostream &out, void *user_data, const char *parameter )
915 {
916   HDRHTMLSet *hdrhtml_set = (HDRHTMLSet*)user_data;
917 
918   if( parameter != NULL ) {
919 
920     list<HDRHTMLImage>::iterator it;
921     for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) {
922       if( it->base_name.compare( parameter ) == 0 )
923         break;
924     }
925     if( it == hdrhtml_set->image_list.end() )
926       std::cerr << "Warning: image '" << parameter << "' not found\n";
927 
928     print_image_htmlcode( out, hdrhtml_set, *it );
929 
930   } else {
931 
932     list<HDRHTMLImage>::iterator it;
933     for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); it++ ) {
934 
935       print_image_htmlcode( out, hdrhtml_set, *it );
936 
937     }
938   }
939 
940 }
941 
print_cf_table(ostream & out,void * user_data,const char * parameter)942 void print_cf_table( ostream &out, void *user_data, const char *parameter )
943 {
944   CSVTable *cf = (CSVTable*)user_data;
945 
946   out << "var cf = new Array(\n";
947   for( int b=0; b < cf->rows; b++ ) {
948     out << "   new Array(";
949     for( int ex=0; ex < cf->columns; ex++ ) {
950       out << ' ' << cf->data[ex][b];
951       if( ex != cf->columns-1 )
952         out << ',';
953     }
954     out << ')';
955     if( b != cf->rows-1 )
956       out << ',';
957     out << "\n";
958   }
959   out << ");\n";
960 
961 }
962 
963 
964