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