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: display_adaptive_tmo.cpp,v 1.26 2013/12/28 14:00:54 rafm Exp $
30  */
31 
32 #include <stdlib.h>
33 #include <math.h>
34 #include <algorithm>
35 #include <iostream>
36 #include <memory.h>
37 #include <assert.h>
38 #include <memory>
39 
40 #include "display_adaptive_tmo.h"
41 
42 #include <gsl/gsl_vector.h>
43 #include <gsl/gsl_matrix.h>
44 #include <gsl/gsl_blas.h>
45 #include <gsl/gsl_interp.h>
46 #include "cqp/gsl_cqp.h"
47 
48 #include <config.h>
49 
50 
51 // Computing Conditional Density takes about 90% of the time
52 #define PROGRESS_CDF 90
53 
54 #define MIN_PHVAL 1e-8f          // Minimum value allowed in HDR images
55 #define MAX_PHVAL 1e8f           // Maximum value allowed in HDR images
56 
57 /**
58  *  Simple RAII wrapper for gsl_matrix
59  */
60 class auto_matrix
61 {
62       gsl_matrix *m;
63 
64    public:
auto_matrix(gsl_matrix * m)65       auto_matrix(gsl_matrix *m): m(m) {}
~auto_matrix()66       ~auto_matrix() { gsl_matrix_free(m); }
operator gsl_matrix*()67       operator gsl_matrix* () { return m; }
68 };
69 
70 /**
71  *  Simple RAII wrapper for gsl_vector
72  */
73 class auto_vector
74 {
75       gsl_vector *v;
76 
77    public:
auto_vector(gsl_vector * v)78       auto_vector(gsl_vector *v): v(v) {}
~auto_vector()79       ~auto_vector() { gsl_vector_free(v); }
operator gsl_vector*()80       operator gsl_vector* () { return v; }
81 };
82 
83 /**
84  *  Simple RAII wrapper for gsl_cqpminimizer
85  */
86 class auto_cqpminimizer
87 {
88       gsl_cqpminimizer *v;
89 
90    public:
auto_cqpminimizer(gsl_cqpminimizer * v)91       auto_cqpminimizer(gsl_cqpminimizer *v): v(v) {}
~auto_cqpminimizer()92       ~auto_cqpminimizer() { gsl_cqpminimizer_free(v); }
operator gsl_cqpminimizer*()93       operator gsl_cqpminimizer* () { return v; }
94 };
95 
96 
97 
98 // =============== Utils ==============
99 
100 #define round_int( x ) (int)((x) + 0.5)
101 #define sign( x ) ( (x)<0 ? -1 : 1 )
102 
safe_log10(float x,const float min_x=MIN_PHVAL,const float max_x=MAX_PHVAL)103 inline float safe_log10( float x, const float min_x = MIN_PHVAL, const float max_x = MAX_PHVAL )
104 {
105   if( x < min_x )
106     return log10(min_x);
107   else if( x > max_x )
108     return log10(max_x);
109   else return log10( x );
110 }
111 
112 /**
113  * Find the lowest non-zero value. Used to avoid log10(0).
114  */
min_positive(const float * x,size_t len)115 float min_positive( const float *x, size_t len )
116 {
117   float min_val = MAX_PHVAL;
118   for( int k=0; k < len; k++ )
119     if( unlikely(x[k] < min_val && x[k] > 0) )
120       min_val = x[k];
121 
122   return min_val;
123 }
124 
125 
mult_rows(const gsl_matrix * A,const gsl_vector * b,gsl_matrix * C)126 void mult_rows( const gsl_matrix *A, const gsl_vector *b, gsl_matrix *C )
127 {
128   assert( A->size1 == b->size );
129   for( int j=0; j < A->size2; j++ )
130     for( int i=0; i < A->size1; i++ )
131       gsl_matrix_set( C, i, j, gsl_matrix_get(A,i,j)*gsl_vector_get(b,i) );
132 }
133 
134 /**
135  * Lookup table on a uniform array & interpolation
136  *
137  * x_i must be at least two elements
138  * y_i must be initialized after creating an object
139  */
140 class UniformArrayLUT
141 {
142   const double *x_i;
143   size_t lut_size;
144   double delta;
145 
146   bool own_y_i;
147 public:
148   double *y_i;
149 
UniformArrayLUT(size_t lut_size,const double * x_i,double * y_i=NULL)150   UniformArrayLUT( size_t lut_size, const double *x_i, double *y_i = NULL ) : x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] )
151   {
152     if( y_i == NULL ) {
153       this->y_i = new double[lut_size];
154       own_y_i = true;
155     } else {
156       this->y_i = y_i;
157       own_y_i = false;
158     }
159   }
160 
UniformArrayLUT()161   UniformArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ), delta( 0. ) {}
162 
UniformArrayLUT(const UniformArrayLUT & other)163   UniformArrayLUT(const UniformArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta )
164   {
165      this->y_i = new double[lut_size];
166      own_y_i = true;
167      memcpy(this->y_i, other.y_i, lut_size * sizeof(double));
168   }
169 
operator =(const UniformArrayLUT & other)170       UniformArrayLUT& operator = (const UniformArrayLUT& other)
171       {
172          this->lut_size = other.lut_size;
173          this->delta = other.delta;
174          this->x_i = other.x_i;
175 	 this->y_i = new double[lut_size];
176 	 own_y_i = true;
177 	 memcpy(this->y_i, other.y_i, lut_size * sizeof(double));
178 	 return *this;
179       }
180 
~UniformArrayLUT()181   ~UniformArrayLUT()
182   {
183     if( own_y_i )
184       delete []y_i;
185   }
186 
interp(double x)187   double interp( double x )
188   {
189     const double ind_f = (x - x_i[0])/delta;
190     const size_t ind_low = (size_t)(ind_f);
191     const size_t ind_hi = (size_t)ceil(ind_f);
192 
193     if( unlikely(ind_f < 0) )           // Out of range checks
194       return y_i[0];
195     if( unlikely(ind_hi >= lut_size) )
196       return y_i[lut_size-1];
197 
198     if( unlikely(ind_low == ind_hi) )
199       return y_i[ind_low];      // No interpolation necessary
200 
201     return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(double)ind_low); // Interpolation
202   }
203 
204 };
205 
206 #define PFSEOL "\x0a"
dumpPFS(const char * fileName,const int width,const int height,float * data,const char * channelName)207 static void dumpPFS( const char *fileName, const int width, const int height, float *data, const char *channelName )
208 {
209   FILE *fh = fopen( fileName, "wb" );
210   assert( fh != NULL );
211 
212   fprintf( fh, "PFS1" PFSEOL "%d %d" PFSEOL "1" PFSEOL "0" PFSEOL
213     "%s" PFSEOL "0" PFSEOL "ENDH", width, height, channelName );
214 
215   for( int y = 0; y < height; y++ )
216     for( int x = 0; x < width; x++ ) {
217       fwrite( &data[x+y*width], sizeof( float ), 1, fh );
218     }
219 
220   fclose( fh );
221 }
222 
223 
compute_gaussian_level(const int width,const int height,const pfstmo::Array2D & in,pfstmo::Array2D & out,int level,pfstmo::Array2D & temp)224 void compute_gaussian_level( const int width, const int height, const pfstmo::Array2D& in, pfstmo::Array2D& out, int level, pfstmo::Array2D& temp )
225 {
226 
227   float kernel_a = 0.4;
228 
229   const int kernel_len = 5;
230   const int kernel_len_2 = kernel_len/2;
231   float kernel[kernel_len] = { 0.25f - kernel_a/2, 0.25f , kernel_a, 0.25f, 0.25f - kernel_a/2 };
232 
233   int step = 1<<level;
234 
235   // FIXME: without the following (with using operator () ) there
236   // seems to be a performance drop - need to investigate if gcc can
237   // be made to optimize this better.  If this can't be made faster
238   // with using the overloaded operator then this optimization should
239   // be applied to the other TMOs as well.
240   const float* in_raw = in.getRawData();
241   float* temp_raw = temp.getRawData();
242   float* out_raw = out.getRawData();
243 
244   // Filter rows
245   for( int r=0; r < height; r++ ) {
246     for( int c=0; c < width; c++ ) {
247       float sum = 0;
248       for( int j=0; j< kernel_len; j++ ) {
249         int l = (j-kernel_len_2)*step+c;
250         if( unlikely( l < 0 ) )
251           l = -l;
252         if( unlikely( l >= width ) )
253           l = 2*width - 2 - l;
254         sum += in_raw[r*width+l] * kernel[j];
255       }
256       temp_raw[r*width+c] = sum;
257     }
258   }
259 
260   // Filter columns
261   for( int c=0; c < width; c++ ) {
262     for( int r=0; r < height; r++ ) {
263       float sum = 0;
264       for( int j=0; j< kernel_len; j++ ) {
265         int l = (j-kernel_len_2)*step+r;
266         if( unlikely(l < 0) )
267           l = -l;
268         if( unlikely(l >= height) )
269           l = 2*height - 2 - l;
270         sum += temp_raw[l*width+c] * kernel[j];
271       }
272       out_raw[r*width+c] = sum;
273     }
274   }
275 }
276 
clamp_channel(const float v)277 inline float clamp_channel( const float v )
278 {
279   return (v > MIN_PHVAL ? v : MIN_PHVAL);
280 }
281 
print_matrix(gsl_matrix * m)282 void print_matrix( gsl_matrix *m )
283 {
284   for( int r=0; r < m->size1; r++ ) {
285     fprintf( stderr, "[ " );
286     for( int c=0; c< m->size2; c++ ) {
287       fprintf( stderr, "%g ", gsl_matrix_get( m, r, c ) );
288     }
289     fprintf( stderr, "]\n" );
290   }
291 }
292 
print_vector(gsl_vector * v)293 void print_vector( gsl_vector *v )
294 {
295   for( int r=0; r < v->size; r++ ) {
296     fprintf( stderr, "[ %g\t ]\n ", gsl_vector_get( v, r ) );
297   }
298 }
299 
300 /** Compute conditional probability density function
301  */
302 
303 
304 // round_int( (l_max-l_min)/delta ) + 1;
305 #define X_COUNT (round_int((8.f+8.f)/0.1) + 1)
306 
307 class conditional_density: public datmoConditionalDensity
308 {
309 public:
310   static const double l_min, l_max, delta;
311   static double x_scale[X_COUNT];    // input log luminance scale
312   double *g_scale;    // contrast scale
313   double *f_scale;    // frequency scale
314 
315   const double g_max;
316 
317   double total;
318 
319   int x_count, g_count, f_count; // Number of elements
320 
321   double *C;          // Conditional probability function
322 
conditional_density(const float pix_per_deg=30.f)323   conditional_density( const float pix_per_deg = 30.f ) :
324     g_max( 0.7f )
325   {
326 
327     x_count = X_COUNT;
328     g_count = round_int(g_max/delta)*2 + 1;
329 
330     // Find freq. band < 3 cyc per deg
331     int f;
332     for( f=0; f<8; f++ ) {
333       float b_freq = 0.5f*pix_per_deg/(float)(1<<f);
334       if( b_freq <= 3 )
335         break;
336     }
337     f_count = f+1;
338 
339     g_scale = new double[g_count];
340     f_scale = new double[f_count];
341 
342     C = new double[x_count*g_count*f_count];
343     memset( C, 0, x_count*g_count*f_count*sizeof(double) );
344 
345     if( x_scale[0] == 0 ) {
346       for( int i=0; i<x_count; i++ )
347         x_scale[i] = l_min + delta*i;
348     }
349 
350     for( int i=0; i<g_count; i++ )
351       g_scale[i] = -g_max + delta*i;
352 
353     for( int i=0; i<f_count; i++ )
354       f_scale[i] = 0.5f*pix_per_deg/(float)(1<<i);
355 
356   }
357 
~conditional_density()358   ~conditional_density()
359   {
360     delete []C;
361     delete []g_scale;
362     delete []f_scale;
363   }
364 
operator ()(int x,int g,int f)365   double& operator()( int x, int g, int f )
366   {
367     assert( (x + g*x_count + f*x_count*g_count >= 0) && (x + g*x_count + f*x_count*g_count < x_count*g_count*f_count) );
368     return C[x + g*x_count + f*x_count*g_count];
369   }
370 
371 };
372 
~datmoConditionalDensity()373 datmoConditionalDensity::~datmoConditionalDensity()
374 {
375 }
376 
377 const double conditional_density::l_min = -8.f, conditional_density::l_max = 8.f, conditional_density::delta = 0.1f;
378 double conditional_density::x_scale[X_COUNT] = { 0 };    // input log luminance scale
379 
380 
datmo_compute_conditional_density(int width,int height,const float * L,pfstmo_progress_callback progress_cb)381 std::auto_ptr<datmoConditionalDensity> datmo_compute_conditional_density( int width, int height, const float *L, pfstmo_progress_callback progress_cb )
382 {
383   if( progress_cb != NULL ) {
384     progress_cb( 0 );
385   }
386 
387   pfstmo::Array2D buf_1(width, height);
388   pfstmo::Array2D buf_2(width, height);
389   pfstmo::Array2D temp(width, height);
390 
391   std::auto_ptr<conditional_density> C(new conditional_density());
392 
393   const float thr = 0.0043; // Approx. discrimination threshold in log10
394   const int pix_count = width*height;
395 
396 
397   pfstmo::Array2D* LP_low = &buf_1;
398   pfstmo::Array2D* LP_high = &buf_2;
399 
400   const float min_val = std::max( min_positive( L, pix_count ), MIN_PHVAL );
401 
402   // Compute log10 of an image
403   for( int i=0; i < pix_count; i++ )
404     (*LP_high)(i) = safe_log10( L[i], min_val );
405 
406   bool warn_out_of_range = false;
407   C->total = 0;
408 
409   for( int f=0; f<C->f_count; f++ ) {
410 
411     compute_gaussian_level( width, height, *LP_high, *LP_low, f, temp );
412 
413 // For debug purposes only
414 //    char fname[20];
415 //    sprintf( fname, "l_%d.pfs", f+1 );
416 //    dumpPFS( fname, width, height, LP_low, "Y" );
417 
418     const int gi_tp = C->g_count/2+1;
419     const int gi_tn = C->g_count/2-1;
420     const int gi_t = C->g_count/2;
421     for( int i=0; i < pix_count; i++ ) {
422       float g = (*LP_high)(i) - (*LP_low)(i); // Compute band-pass
423       int x_i = round_int( ((*LP_low)(i) - C->l_min)/C->delta );
424       if( unlikely(x_i < 0 || x_i >= C->x_count) ) {
425         warn_out_of_range = true;
426         continue;
427       }
428       int g_i = round_int( (g + C->g_max) / C->delta );
429       if( unlikely(g_i < 0 || g_i >= C->g_count) )
430         continue;
431 
432       if( g > thr && g < C->delta/2 ) {
433         // above the threshold +
434         (*C)(x_i,gi_tp,f)++;
435       } else if( g < -thr && g > -C->delta/2 ) {
436         // above the threshold -
437         (*C)(x_i,gi_tn,f)++;
438       } else (*C)(x_i,g_i,f)++;
439     }
440 
441     for( int i = 0; i < C->x_count; i++ ) {
442       // Special case: flat field and no gradients
443       if( (*C)(i,gi_t,f) == 0 )
444         continue;
445       bool gradient_exist = false;
446       for( int m=0; m<C->g_count; m++ )
447         if( m != gi_t && (*C)(i,m,f) != 0 ) {
448           gradient_exist = true;
449           break;
450         }
451       if( ~gradient_exist ) {
452         // generate some gradient data to avoid bad conditioned problem
453         (*C)(i,gi_tp,f)++;
454         (*C)(i,gi_tn,f)++;
455       }
456 
457       // Compute C->total
458       for( int m=0; m<C->g_count; m++ )
459         if( likely(m != gi_t) )
460           C->total += (*C)(i,m,f);
461     }
462 
463     std::swap( LP_low, LP_high );
464 
465     if( progress_cb != NULL ) {
466       progress_cb( (f+1)*PROGRESS_CDF/C->f_count );
467     }
468   }
469 
470   if( warn_out_of_range )
471     std::cerr << "Warning: Luminance value out of permissible range\n";
472 
473 
474   // For debugging purposes only
475 //    FILE *fh = fopen( "c_dens.dat", "wt" );
476 //    std::cerr << "x: " << C->x_count << " g: " << C->g_count << " f: " << C->f_count << "\n";
477 //    for( int i=0; i<(C->x_count*C->g_count*C->f_count); i++ ) {
478 //      fprintf( fh, "%g\n", C->C[i] );
479 //    }
480 //    fclose( fh );
481 
482   return (std::auto_ptr<datmoConditionalDensity>)C;
483 }
484 
485 
486 
487 // =============== Quadratic programming solver ==============
488 
489 const static gsl_matrix null_matrix = {0};
490 const static gsl_vector null_vector = {0};
491 
492 /* objective function: 0.5*(x^t)Qx+(q^t)x */
493 /* constraints: Cx>=d */
494 /* Ax=b; is not used in our problem */
solve(gsl_matrix * Q,gsl_vector * q,gsl_matrix * C,gsl_vector * d,gsl_vector * x)495 int solve( gsl_matrix *Q, gsl_vector *q, gsl_matrix *C, gsl_vector *d, gsl_vector *x)
496 {
497 
498   gsl_cqp_data cqpd;
499 
500   cqpd.Q = Q;
501   cqpd.q = q;
502 
503   // Do not use any equality constraints (Ax=b)
504 
505   // Unfortunatelly GSL does not allow for 0-size vectors and matrices
506   // As a work-around we create a phony gsl_matrix that has 0-size.
507   // This matrix must not be passed to any gsl function!
508 
509   cqpd.A = &null_matrix;
510   cqpd.b = &null_vector;
511 
512   cqpd.C = C;
513   cqpd.d = d;
514 
515   size_t n = cqpd.Q->size1;
516   size_t me = cqpd.b->size;
517   size_t mi = cqpd.d->size;
518 
519   const size_t max_iter = 100;
520 
521   size_t iter=1;
522 
523   int status;
524 
525   const gsl_cqpminimizer_type * T;
526 
527   T = gsl_cqpminimizer_mg_pdip;
528   auto_cqpminimizer s(gsl_cqpminimizer_alloc(T, n, me, mi));
529 
530   status = gsl_cqpminimizer_set(s, &cqpd);
531 
532   bool verbose = false;
533 
534   if( verbose ) fprintf( stderr, "== Itn ======= f ======== ||gap|| ==== ||residual||\n\n");
535 
536   do
537   {
538     status = gsl_cqpminimizer_iterate(s);
539     status = gsl_cqpminimizer_test_convergence(s, 1e-10, 1e-10);
540 
541     if( verbose ) fprintf( stderr, "%4d   %14.8f  %13.6e  %13.6e\n", (int)iter, gsl_cqpminimizer_f(s), gsl_cqpminimizer_gap(s), gsl_cqpminimizer_residuals_norm(s));
542 
543     if(status == GSL_SUCCESS)
544     {
545       size_t j;
546       if( verbose ) {
547         fprintf( stderr, "\nMinimum is found at\n");
548         for(j=0; j<gsl_cqpminimizer_x(s)->size; j++)
549           fprintf( stderr, "%9.6f ",gsl_vector_get(gsl_cqpminimizer_x(s), j));
550         fprintf( stderr, "\n\n");
551       }
552 
553 
554 //       printf("\nLagrange-multipliers for Ax=b\n");
555 //       for(j=0; j<gsl_cqpminimizer_lm_eq(s)->size; j++)
556 //         printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_eq(s), j));
557 //       printf("\n\n");
558 
559 //       printf("\nLagrange-multipliers for Cx>=d\n");
560 //       for(j=0; j<gsl_cqpminimizer_lm_ineq(s)->size; j++)
561 //         printf("%9.6f ",gsl_vector_get(gsl_cqpminimizer_lm_ineq(s), j));
562 //       printf("\n\n");
563     }
564     else
565     {
566       iter++;
567     }
568 
569   }
570   while(status == GSL_CONTINUE && iter<=max_iter);
571 
572   bool valid_solution = true;
573 
574   // If the solver bahaves instable, stop at this point
575   if( status != GSL_SUCCESS ) {
576     if( gsl_cqp_minimizer_test_infeasibility( s, 1e-10 ) != GSL_SUCCESS )
577       valid_solution = false;
578   }
579 
580   if( valid_solution )
581     gsl_vector_memcpy( x, gsl_cqpminimizer_x(s) );
582 
583 
584   return GSL_SUCCESS;
585 }
586 
587 
588 
589 // =============== HVS functions ==============
590 
contrast_transducer_kulikowski(double C,double sensitivity,datmoVisualModel visual_model)591 double contrast_transducer_kulikowski( double C, double sensitivity, datmoVisualModel visual_model )
592 {
593   if( visual_model & vm_contrast_masking ) {
594     float Mt = std::min( 0.99, 1./sensitivity ); // threshold as Michelson contrast
595     float Gt = 0.5 * log10( (Mt+1)/(1-Mt) ); // threshold as log contrast
596 
597     return sign(C) * powf( abs(C)-Gt+1, 0.5 );
598   } else {
599     return C * sensitivity;
600   }
601 
602 }
603 
contrast_transducer(double C,double sensitivity,datmoVisualModel visual_model)604 double contrast_transducer( double C, double sensitivity, datmoVisualModel visual_model )
605 {
606   if( visual_model & vm_contrast_masking ) {
607 
608     if( 0 ) // TODO: research this topic some more
609       return contrast_transducer_kulikowski( C, sensitivity, visual_model );
610     else {
611 
612     const double W = pow( 10, fabs(C) ) - 1.;
613 
614     const double Q = 3., A = 3.291, B = 3.433, E = 0.8, k=0.2599;
615     const double SC = sensitivity*W;
616 
617     return sign(C) * A*(pow(1.+pow(SC,Q),1./3.)-1.)/(k*pow(B+SC,E));
618 
619     }
620 
621   } else {
622     return C * sensitivity;
623   }
624 
625 }
626 
627 
628 
629 /**
630  * Contrast Sensitivity Function from Daly's VDP paper
631  *
632  * @param rho  spatial frequency in cycles per degree
633  * @param theta  spatial angle in radians
634  * @param l_adapt  luminance of adaptation
635  * @param img_size  image size given in visual degrees^2
636  * @param viewing_dist  viewing distance in meters (default = 0.5m)
637  * @return sensitivity
638  */
csf_daly(double rho,double theta,double l_adapt,double im_size,double viewing_dist=0.5)639 double csf_daly( double rho, double theta, double l_adapt, double im_size, double viewing_dist = 0.5 )
640 {
641   if( rho == 0 )
642     return 0;                   // To avoid singularity
643 
644   // Sensitivity calibration constant (from Daly's paper: 250)
645   const double P = 250.f;
646 
647   const double eps = 0.9;
648   const double i_pow2 = im_size;
649   const double l = l_adapt;
650   const double A = 0.801 * pow(1 + 0.7 / l, -0.20);
651   const double B = 0.3 * pow(1 + 100 / l, 0.15);
652 
653   const double r_a = 0.856 * powf(viewing_dist, 0.14);
654   const double e = 0.0;     // eccentricity in visual degrees
655   const double r_e = 1.0 / (1.0 + 0.24 * e);
656   const double r_a_r_e = r_a * r_e;
657 
658   double S1, S2;
659 
660   double B1 = B*eps*rho;
661   S1 = pow(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)*
662             A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1));
663 
664   const double ob=0.78;
665   const double r_theta = (1-ob)/2 * cosf(4.0 * theta) + (1+ob)/2;
666   rho = rho / (r_a_r_e * r_theta);
667 
668   B1 = B*eps*rho;
669   S2 = powf(pow(3.23 * pow(rho*rho * i_pow2, -0.3), 5.0) + 1.0, -0.2)*
670             A*eps*rho * exp(-B1) * sqrt(1 + 0.06*exp(B1));
671 
672   return (S1 > S2 ? S2 : S1) * P;
673 }
674 
csf_hdrvdp(double rho,double l_adapt)675 double csf_hdrvdp( double rho, double l_adapt )
676 {
677 
678   static const float csf_pars[][5] = {
679     { 0.0160737,   0.991265,   3.74038,   0.50722,   4.46044 },
680     { 0.383873,   0.800889,   3.54104,   0.682505,   4.94958 },
681     { 0.929301,   0.476505,   4.37453,   0.750315,   5.28678 },
682     { 1.29776,   0.405782,   4.40602,   0.935314,   5.61425 },
683     { 1.49222,   0.334278,   3.79542,   1.07327,   6.4635 },
684     { 1.46213,   0.394533,   2.7755,   1.16577,   7.45665 } };
685 
686   return 0;
687 
688 
689 }
690 
691 
csf_datmo(double rho,double l_adapt,datmoVisualModel visual_model)692 double csf_datmo( double rho, double l_adapt, datmoVisualModel visual_model )
693 {
694 
695   if( !(visual_model & vm_luminance_masking ) )
696     l_adapt = 1000.;
697   if( !(visual_model & vm_csf ) )
698     rho = 4.;
699 
700   return csf_daly( rho, 0, l_adapt, 1 );
701 }
702 
compute_y(double * y,const gsl_vector * x,int * skip_lut,int x_count,int L,double Ld_min,double Ld_max)703 void compute_y( double *y, const gsl_vector *x, int *skip_lut, int x_count, int L, double Ld_min, double Ld_max )
704 {
705   double sum_d = 0;
706   double alpha = 1;
707   for( int k=0; k < L; k++ )
708     sum_d += gsl_vector_get( x, k );
709   double cy = log10(Ld_min) + alpha*(log10(Ld_max)-log10(Ld_min) - sum_d);
710   double dy;
711   y[0] = cy;
712   dy = 0;
713   for( int i=0; i < x_count-1; i++ ) {
714     if( skip_lut[i] != -1 ) {
715       // Check how many nodes spans this d_i
716       int j;
717       for( j = i+1; j < (x_count-1) && skip_lut[j] == -1; j++ );
718 
719       if( j == (x_count-1) ) { // The last node
720         dy = 0;
721         y[i] = cy;
722         cy += gsl_vector_get( x, skip_lut[i] );
723         continue;
724       } else
725         dy = gsl_vector_get( x, skip_lut[i] ) / (double)(j-i);
726     }
727     y[i] = cy;
728     cy += dy;
729   }
730   y[x_count-1] = cy;
731 
732 }
733 
734 // =============== Tone mapping ==============
735 
736 /**
737  * Solve the quadratic programming problem to find the optimal tone
738  * curve for the given conditional denstity structure.
739  *
740  * @param y output luminance value for the nodes C->x_scale. y must be
741  * a pre-allocated array and has the same size as C->x_scale.
742  */
optimize_tonecurve(datmoConditionalDensity * C_pub,DisplayFunction * dm,DisplaySize * ds,float enh_factor,double * y,const float white_y,pfstmo_progress_callback progress_cb=NULL,datmoVisualModel visual_model=vm_full,double scene_l_adapt=1000)743 int optimize_tonecurve( datmoConditionalDensity *C_pub, DisplayFunction *dm, DisplaySize *ds,
744   float enh_factor, double *y, const float white_y, pfstmo_progress_callback progress_cb = NULL, datmoVisualModel visual_model = vm_full, double scene_l_adapt = 1000 ) {
745 
746   conditional_density *C = (conditional_density*)C_pub;
747 
748   double d_dr = log10( dm->display( 1.f )/dm->display( 0.f ) ); // display dynamic range
749 
750   // Create LUTs for CSF to speed up computations
751   UniformArrayLUT *csf_lut = new UniformArrayLUT[C->f_count];
752   for( int f = 0; f < C->f_count; f++ ) {
753     csf_lut[f] = UniformArrayLUT( C->x_count, C->x_scale );
754     for( int i=0; i < C->x_count; i++ )
755       csf_lut[f].y_i[i] = csf_datmo( C->f_scale[f], pow( 10., C->x_scale[i] ), visual_model );
756   }
757 
758   const int max_neigh = (C->g_count-1)/2;
759 
760   // count number of needed equations and remove not used variables
761   // Create a structure of disconnected frameworks, which can be connected later
762   int k = 0;
763   int skip_lut[C->x_count-1]; // LUT used to skip unused nodes
764   int used_var[C->x_count-1]; // Used / unused variables
765   memset( used_var, 0, sizeof( int )*(C->x_count-1) );
766   int minmax_i[2] = { C->x_count-1, 0 };
767 
768   for( int f=0; f < C->f_count; f++ )
769     for( int i=0; i < C->x_count; i++ )
770       for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) {
771         if( i == j || (*C)(i,j-i+max_neigh,f) == 0 )
772           continue;
773         k++;
774 
775         const int from = std::min(i,j);
776         const int to = std::max(i,j);
777         size_t use_fwrk = 0;
778         for( int l = from; l <= to-1; l++ ) {
779           used_var[l] = 1;
780         }
781         minmax_i[0] = std::min( minmax_i[0], from );
782         minmax_i[1] = std::max( minmax_i[1], to-1 );
783       }
784 
785   int white_i;
786   if( white_y > 0 ) {
787     k++;
788     const float white_l = log10( white_y );
789     // find i that corresponds to the reference white
790     int i;
791     for( i = C->x_count-1; i >= 0; i-- )
792       if( C->x_scale[i] <= white_l )
793         break;
794     white_i = i;
795     used_var[white_i] = 1;
796     minmax_i[0] = std::min( minmax_i[0], white_i );
797     minmax_i[1] = std::max( minmax_i[1], white_i );
798   }
799 
800   // Create skip lookup table (to remove selected columns that contain all zeros)
801   int i = 0;
802   int fwrk = 0;                 // Number if missing contrast ranges
803   for( int l = 0; l < C->x_count-1; l++ ) {
804     if( l < minmax_i[0] || l > minmax_i[1] ) {
805       skip_lut[l] = -1;
806       continue;
807     }
808     if( !used_var[l] ) {
809       if( l>0 && !used_var[l-1] ) {
810         skip_lut[l] = -1;
811         continue;
812       }
813       fwrk++;
814     }
815     skip_lut[l] = i++;
816   }
817 
818   const int M = k+fwrk; // number of equations
819   const int L = i; // Number of non-zero d_i variables
820 
821 // Constraints
822 // all intervals must be >=0
823 // sum of intervals must be equal to a displayable dynamic range
824 
825   // Ale = [eye(interval_count); -ones(1,interval_count)];
826 
827   auto_matrix Ale(gsl_matrix_calloc(L+1, L));
828   gsl_matrix_set_identity( Ale );
829   gsl_matrix_view lower_row = gsl_matrix_submatrix( Ale, L, 0, 1, L );
830   gsl_matrix_set_all( &lower_row.matrix, -1 );
831 
832   // ble = [zeros(interval_count,1); -d_dr];
833   auto_vector ble(gsl_vector_calloc(L+1));
834   gsl_vector_set( ble, L, -d_dr );
835 
836   auto_matrix A(gsl_matrix_calloc(M, L));
837   auto_vector B(gsl_vector_alloc(M));
838   auto_vector N(gsl_vector_alloc(M));
839   size_t band[M]; // Frequency band (index)
840   size_t back_x[M]; // Background luminance (index)
841 
842   k = 0;
843   for( int f=0; f < C->f_count; f++ ) {
844     double sensitivity;
845     if( scene_l_adapt != -1 )
846       sensitivity = csf_datmo( C->f_scale[f], scene_l_adapt, visual_model );
847 
848     for( int i=0; i < C->x_count; i++ )
849       for( int j = std::max(0,i-max_neigh); j < std::min(C->x_count-1,i+max_neigh); j++ ) {
850         if( i == j || (*C)(i,j-i+max_neigh,f) == 0 )
851           continue;
852 
853         const int from = std::min(i,j);
854         const int to = std::max(i,j);
855 
856 //      A(k,min(i,j):(max(i,j)-1)) = 1;
857         for( int l = from; l <= to-1; l++ ) {
858           if( skip_lut[l] == -1 )
859             continue;
860           gsl_matrix_set( A, k, skip_lut[l], 1 );
861         }
862 
863         if( scene_l_adapt == -1 ) {
864           sensitivity = csf_lut[f].interp( C->x_scale[from] );
865         }
866 
867 //      B(k,1) = l_scale(max(i,j)) - l_scale(min(i,j));
868         gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) );
869 
870 
871 //      N(k,k) = jpf(j-i+max_neigh+1,i,band);
872         gsl_vector_set( N, k, (*C)(i,j-i+max_neigh,f) );
873 
874         band[k] = f;
875         back_x[k] = i;
876 
877         k++;
878       }
879   }
880 
881   if( white_y > 0 ) {
882     for( int l = white_i; l < C->x_count-1; l++ ) {
883       if( skip_lut[l] == -1 )
884         continue;
885       gsl_matrix_set( A, k, skip_lut[l], 1 );
886     }
887     gsl_vector_set( B, k, 0 );
888     gsl_vector_set( N, k, C->total * 0.1 ); // Strength of reference white anchoring
889     band[k] = 0;
890     back_x[k] = white_i;
891     k++;
892   }
893 
894   // Connect disconnected frameworks
895   // This is the case when there is no contrast between some patches in an image
896   {
897     for( int i = minmax_i[0]; i <= minmax_i[1]; i++ ) {
898       if( !used_var[i] ) {
899         const int from = i;
900         int to = i+1;
901         while( !used_var[to] )
902           to++;
903         assert( k < M );
904         for( int l = from; l <= to-1; l++ ) {
905           if( skip_lut[l] == -1 )
906             continue;
907           gsl_matrix_set( A, k, skip_lut[l], 1 );
908         }
909 
910         double sensitivity;
911         if( scene_l_adapt == -1 ) {
912           sensitivity = csf_lut[C->f_count-1].interp( C->x_scale[from] );
913         } else
914           sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model );
915 
916 
917 //        const double sensitivity = csf_datmo( C->f_scale[C->f_count-1], scene_l_adapt, visual_model );
918 
919         gsl_vector_set( B, k, contrast_transducer( (C->x_scale[to] - C->x_scale[from])*enh_factor, sensitivity, visual_model ) );
920 
921         gsl_vector_set( N, k, C->total * 0.1 ); // Strength of framework anchoring
922         band[k] = C->f_count-1;
923         back_x[k] = to;
924         k++;
925         i = to;
926       }
927     }
928   }
929 
930   auto_matrix H(gsl_matrix_alloc(L, L));
931   auto_vector f(gsl_vector_alloc(L));
932   auto_matrix NA(gsl_matrix_alloc(M, L));
933   auto_matrix AK(gsl_matrix_alloc(M, L));
934   auto_vector Ax(gsl_vector_alloc(M));
935   auto_vector K(gsl_vector_alloc(M));
936   auto_vector x(gsl_vector_alloc(L));
937   auto_vector x_old(gsl_vector_alloc(L));
938 
939   gsl_vector_set_all( x, d_dr/L );
940 
941 
942   int max_iter = 200;
943   if( !(visual_model & vm_contrast_masking) )
944     max_iter = 1;
945 
946   for( int it = 0; it < max_iter; it++ ) {
947 
948 //    fprintf( stderr, "Iteration #%d\n", it );
949 
950     // Compute y values for the current solution
951     compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) );
952 
953     // Ax = A*x
954     gsl_blas_dgemv( CblasNoTrans, 1, A, x, 0, Ax );
955 
956     // T(rng{band}) = cont_transd( Ax(rng{band}), band, DD(rng{band},:)*y' ) ./ Axd(rng{band});
957     for( int k=0; k < M; k++ ){
958       const double Ax_k = gsl_vector_get( Ax, k );
959       const double denom = (fabs(Ax_k) < 0.0001 ? 1. : Ax_k );
960 //      if( !(visual_model & vm_contrast_masking) ) {
961 //        gsl_vector_set( K, k, 1 );
962 //      } else {
963         double sensitivity = csf_lut[band[k]].interp( y[back_x[k]] );
964         gsl_vector_set( K, k, contrast_transducer( Ax_k, sensitivity, visual_model ) / denom );
965 //      }
966 
967     }
968 
969     // AK = A*K;
970     mult_rows( A, K, AK );
971 
972     // NA = N*A;
973     mult_rows( AK, N, NA );
974 
975     // H = AK'*NA;
976     gsl_blas_dgemm( CblasTrans, CblasNoTrans, 1, AK, NA, 0, H );
977 
978     // f = -B'*NA = - NA' * B;
979     gsl_blas_dgemv( CblasTrans, -1, NA, B, 0, f );
980 
981     gsl_vector_memcpy( x_old, x );
982 
983     solve( H, f, Ale, ble, x );
984 
985     // Check for convergence
986     double min_delta = (C->x_scale[1]-C->x_scale[0])/10.; // minimum acceptable change
987     bool converged = true;
988     for( int i=0; i < L; i++ ) {
989       double delta = fabs( gsl_vector_get(x,i) - gsl_vector_get(x_old,i) );
990       if( delta > min_delta ) {
991         converged = false;
992         break;
993       }
994     }
995     if( converged )
996       break;
997   }
998   if( progress_cb != NULL ) {
999     progress_cb( 95 );
1000   }
1001 
1002 //   for( int i=0; i < L; i++ )
1003 //     fprintf( stderr, "%9.6f ", gsl_vector_get( x, i ) );
1004 //   fprintf( stderr, "\n" );
1005 
1006   compute_y( y, x, skip_lut, C->x_count, L, dm->display(0), dm->display(1) );
1007 
1008   delete [] csf_lut;
1009 
1010   return PFSTMO_OK;
1011 }
1012 
datmo_tonemap(float * R_out,float * G_out,float * B_out,int width,int height,const float * R_in,const float * G_in,const float * B_in,const float * L_in,DisplayFunction * df,DisplaySize * ds,const float enh_factor,const float saturation_factor,const float white_y,pfstmo_progress_callback progress_cb)1013 int datmo_tonemap( float *R_out, float *G_out, float *B_out, int width, int height,
1014   const float *R_in, const float *G_in, const float *B_in, const float *L_in,
1015   DisplayFunction *df, DisplaySize *ds, const float enh_factor, const float saturation_factor,
1016   const float white_y, pfstmo_progress_callback progress_cb )
1017 {
1018   std::auto_ptr<datmoConditionalDensity> C(datmo_compute_conditional_density( width, height, L_in, progress_cb ));
1019 
1020   datmoToneCurve tc;
1021 
1022   int res;
1023 
1024   res = datmo_compute_tone_curve( &tc, C.get(), df, ds, enh_factor, white_y, progress_cb );
1025 
1026   datmo_apply_tone_curve_cc( R_out, G_out, B_out, width, height, R_in, G_in, B_in, L_in, &tc,
1027     df, saturation_factor );
1028 
1029 
1030   if( progress_cb != NULL ) {
1031     progress_cb( 100 );
1032   }
1033 
1034   return PFSTMO_OK;
1035 }
1036 
1037 
datmo_compute_tone_curve(datmoToneCurve * tc,datmoConditionalDensity * cond_dens,DisplayFunction * df,DisplaySize * ds,const float enh_factor,const float white_y,pfstmo_progress_callback progress_cb,datmoVisualModel visualModel,double scene_l_adapt)1038 int datmo_compute_tone_curve( datmoToneCurve *tc, datmoConditionalDensity *cond_dens,
1039   DisplayFunction *df, DisplaySize *ds, const float enh_factor,
1040   const float white_y, pfstmo_progress_callback progress_cb,
1041   datmoVisualModel visualModel, double scene_l_adapt  )
1042 {
1043   conditional_density *C = (conditional_density*)cond_dens;
1044   tc->init( C->x_count, C->x_scale );
1045   return optimize_tonecurve( cond_dens, df, ds, enh_factor, tc->y_i, white_y, progress_cb, visualModel, scene_l_adapt );
1046 }
1047 
1048 
1049 
datmo_apply_tone_curve(float * R_out,float * G_out,float * B_out,int width,int height,const float * R_in,const float * G_in,const float * B_in,const float * L_in,datmoToneCurve * tc,DisplayFunction * df,const float saturation_factor)1050 int datmo_apply_tone_curve( float *R_out, float *G_out, float *B_out, int width, int height,
1051   const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc,
1052   DisplayFunction *df, const float saturation_factor )
1053 {
1054 
1055   // Create LUT: log10( lum factor ) -> pixel value
1056   UniformArrayLUT tc_lut( tc->size, tc->x_i );
1057   for( int i=0; i < tc->size; i++ ) {
1058     tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) );
1059   }
1060 
1061   const size_t pix_count = width*height;
1062   for( size_t i=0; i < pix_count; i++ ) {
1063     float L_fix = clamp_channel(L_in[i]);
1064     const float luma = tc_lut.interp( log10(L_fix) );
1065     R_out[i] = pow( clamp_channel(R_in[i]/L_fix), saturation_factor ) * luma;
1066     G_out[i] = pow( clamp_channel(G_in[i]/L_fix), saturation_factor ) * luma;
1067     B_out[i] = pow( clamp_channel(B_in[i]/L_fix), saturation_factor ) * luma;
1068 
1069   }
1070 
1071   return PFSTMO_OK;
1072 }
1073 
1074 
1075 /**
1076  * Apply tone curve with color correction (http://zgk.wi.ps.pl/color_correction/)
1077  */
datmo_apply_tone_curve_cc(float * R_out,float * G_out,float * B_out,int width,int height,const float * R_in,const float * G_in,const float * B_in,const float * L_in,datmoToneCurve * tc,DisplayFunction * df,const float saturation_factor)1078 int datmo_apply_tone_curve_cc( float *R_out, float *G_out, float *B_out, int width, int height,
1079   const float *R_in, const float *G_in, const float *B_in, const float *L_in, datmoToneCurve *tc,
1080   DisplayFunction *df, const float saturation_factor )
1081 {
1082   // Create LUT: log10( lum factor ) -> pixel value
1083   UniformArrayLUT tc_lut( tc->size, tc->x_i );
1084   for( int i=0; i < tc->size; i++ ) {
1085     tc_lut.y_i[i] = (float)pow( 10, tc->y_i[i] );
1086 //    tc_lut.y_i[i] = df->inv_display( (float)pow( 10, tc->y_i[i] ) );
1087   }
1088 
1089   // Create LUT: log10( lum factor ) -> saturation correction (for the tone-level)
1090   UniformArrayLUT cc_lut( tc->size, tc->x_i );
1091   for( int i=0; i < tc->size-1; i++ ) {
1092     const float contrast = std::max( (tc->y_i[i+1]-tc->y_i[i])/(tc->x_i[i+1]-tc->x_i[i]), 0. );
1093     const float k1 = 1.48;
1094     const float k2 = 0.82;
1095     cc_lut.y_i[i] = ( (1 + k1)*pow(contrast,k2) )/( 1 + k1*pow(contrast,k2) ) * saturation_factor;
1096   }
1097   cc_lut.y_i[tc->size-1] = 1;
1098 
1099   const size_t pix_count = width*height;
1100   for( size_t i=0; i < pix_count; i++ ) {
1101     float L_fix = clamp_channel(L_in[i]);
1102     const float L_out = tc_lut.interp( log10(L_fix) );
1103     const float s = cc_lut.interp( log10(L_fix) ); // color correction
1104     R_out[i] = df->inv_display(powf(clamp_channel(R_in[i]/L_fix), s) * L_out);
1105     G_out[i] = df->inv_display(powf(clamp_channel(G_in[i]/L_fix), s) * L_out);
1106     B_out[i] = df->inv_display(powf(clamp_channel(B_in[i]/L_fix), s) * L_out);
1107   }
1108 
1109   return PFSTMO_OK;
1110 }
1111 
1112 
1113 // =============== Tone-curve filtering ==============
1114 
datmoToneCurve()1115 datmoToneCurve::datmoToneCurve() : x_i( NULL ), y_i( NULL ), own_y_i( false )
1116 {
1117 }
1118 
~datmoToneCurve()1119 datmoToneCurve::~datmoToneCurve()
1120 {
1121   free();
1122 }
1123 
init(size_t n_size,const double * n_x_i,double * n_y_i)1124 void datmoToneCurve::init( size_t n_size, const double *n_x_i, double *n_y_i )
1125 {
1126   free();
1127 
1128   size = n_size;
1129   x_i = n_x_i;
1130   if( n_y_i == NULL ) {
1131     y_i = new double[size];
1132     own_y_i = true;
1133   } else {
1134     y_i = n_y_i;
1135     own_y_i = false;
1136   }
1137 }
1138 
free()1139 void datmoToneCurve::free()
1140 {
1141   if( y_i != NULL && own_y_i )
1142     delete []y_i;
1143 }
1144 
1145 #if 0
1146 
1147 // Depreciated
1148 
1149 #define DATMO_TF_TAPSIZE 26     /* Number of samples required for the temporal filter */
1150 
1151 // Pre-computed FIR filter
1152 double t_filter[DATMO_TF_TAPSIZE] = { 0.0040, 0.0051, 0.0079, 0.0126, 0.0190, 0.0269, 0.0359, 0.0455, 0.0549,
1153                                       0.0635, 0.0706, 0.0757, 0.0784, 0.0784, 0.0757, 0.0706, 0.0635, 0.0549,
1154                                       0.0455, 0.0359, 0.0269, 0.0190, 0.0126, 0.0079, 0.0051, 0.0040 };
1155 void datmo_filter_tone_curves( datmoToneCurve **in_tc, size_t count_in_tc, datmoToneCurve *out_tc )
1156 {
1157   for( int j=0; j < in_tc[0]->size; j++ )
1158     out_tc->y_i[j] = 0;
1159 
1160   for( int t=0; t < DATMO_TF_TAPSIZE; t++ ) {
1161     int at = t;
1162     if( at >= count_in_tc )
1163       at = count_in_tc-1;
1164     for( int j=0; j < in_tc[0]->size; j++ )
1165       out_tc->y_i[j] += t_filter[t] * in_tc[at]->y_i[j];
1166   }
1167 
1168 }
1169 
1170 #endif
1171 
1172 // Pre-computed IIR filters - for different frame rates
1173 double t_filter_a_25fps[] = { 1.000000000000000,  -2.748835809214676,   2.528231219142559,  -0.777638560238080 };
1174 double t_filter_b_25fps[] = { 0.000219606211225409,   0.000658818633676228,   0.000658818633676228,   0.000219606211225409 };
1175 
1176 double t_filter_a_30fps[] = { 1.000000000000000,  -2.790655305284069,   2.602653173508124,  -0.810960871907291 };
1177 double t_filter_b_30fps[] = { 0.000129624539595474,   0.000388873618786423,   0.000388873618786423,   0.000129624539595474 };
1178 
1179 double t_filter_a_60fps[] = { 1.000000000000000,  -2.895292177877897,   2.795994584283360,  -0.900566088981622 };
1180 double t_filter_b_60fps[] = { 0.0000170396779801130, 0.0000511190339403389,   0.0000511190339403389,   0.0000170396779801130 };
1181 
1182 
datmoTCFilter(float fps,float y_min,float y_max)1183 datmoTCFilter::datmoTCFilter( float fps, float y_min, float y_max ) :
1184   fps( fps ), y_min( y_min ), y_max( y_max )
1185 {
1186   assert( fps == 25 || fps == 30 || fps == 60 );
1187   if( fps == 60 ) {
1188     t_filter_a = t_filter_a_60fps;
1189     t_filter_b = t_filter_b_60fps;
1190   } else if( fps == 30 ) {
1191     t_filter_a = t_filter_a_30fps;
1192     t_filter_b = t_filter_b_30fps;
1193   } else {
1194     t_filter_a = t_filter_a_25fps;
1195     t_filter_b = t_filter_b_25fps;
1196   }
1197 
1198   pos = -1;
1199   sz = 0;
1200 }
1201 
getToneCurvePtr()1202 datmoToneCurve *datmoTCFilter::getToneCurvePtr()
1203 {
1204   pos++;
1205   if( pos == DATMO_TF_TAPSIZE )
1206     pos = 0;
1207 
1208   sz++;
1209   if( sz > DATMO_TF_TAPSIZE )
1210     sz = DATMO_TF_TAPSIZE;
1211 
1212   return ring_buffer_org + pos;
1213 }
1214 
filterToneCurve()1215 datmoToneCurve *datmoTCFilter::filterToneCurve()
1216 {
1217   datmoToneCurve *tc_o = ring_buffer_org + pos;
1218   datmoToneCurve *tc_f = ring_buffer_filt + pos;
1219 
1220   tc_f->init( tc_o->size, tc_o->x_i );
1221   if( tc_filt_clamp.x_i == NULL )
1222     tc_filt_clamp.init( tc_o->size, tc_o->x_i );
1223   for( int j=0; j < tc_f->size; j++ )
1224     tc_f->y_i[j] = 0;
1225 
1226   for( int tt=0; tt < DATMO_TF_TAPSIZE; tt++ ) {
1227     datmoToneCurve *x = get_tc(ring_buffer_org,tt);
1228     datmoToneCurve *y;
1229     if( tt >= sz )
1230       y = x;
1231     else
1232       y = get_tc(ring_buffer_filt,tt);
1233 
1234     for( int j=0; j < tc_f->size; j++ ) {
1235       tc_f->y_i[j] += t_filter_b[tt] * x->y_i[j];
1236       if( tt > 0 )
1237         tc_f->y_i[j] -= t_filter_a[tt] * y->y_i[j];
1238     }
1239   }
1240 
1241   // Copy to dest array and clamp
1242   // Note that the clamped values cannot be used for filtering as they
1243   // would cause too much rippling for the IIR filter
1244   for( int j=0; j < tc_f->size; j++ ) {
1245     if( tc_f->y_i[j] < y_min ) {
1246       tc_filt_clamp.y_i[j]  = y_min;
1247     } else if( tc_f->y_i[j] > y_max ) {
1248         tc_filt_clamp.y_i[j] = y_max;
1249     } else
1250       tc_filt_clamp.y_i[j] = tc_f->y_i[j];
1251   }
1252 
1253   return &tc_filt_clamp;
1254 }
1255 
get_tc(datmoToneCurve * ring_buf,int time)1256 datmoToneCurve *datmoTCFilter::get_tc( datmoToneCurve *ring_buf, int time )
1257 {
1258   if( time >= sz )
1259     time = sz-1;
1260 
1261   int p = pos - time;
1262   if( p < 0 )
1263     p = p + DATMO_TF_TAPSIZE;
1264 
1265   return ring_buf + p;
1266 }
1267 
1268 
1269 
1270