/* -*- C++ -*- * * This file is part of RawTherapee. * * Ported from LuminanceHDR by Alberto Griggio * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ /** * @file tmo_fattal02.cpp * @brief TMO: Gradient Domain High Dynamic Range Compression * * Implementation of Gradient Domain High Dynamic Range Compression * by Raanan Fattal, Dani Lischinski, Michael Werman. * * @author Grzegorz Krawczyk, * * * This file is a part of LuminanceHDR package, based on pfstmo. * ---------------------------------------------------------------------- * Copyright (C) 2003,2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * $Id: tmo_fattal02.cpp,v 1.3 2008/11/04 23:43:08 rafm Exp $ */ #ifdef _OPENMP #include #endif #include #include #include #include #include #include #include #include #include #include "array2D.h" #include "improcfun.h" #include "settings.h" #include "iccstore.h" namespace rtengine { extern const Settings *settings; using namespace std; namespace { class Array2Df: public array2D { typedef array2D Super; public: Array2Df(): Super() {} Array2Df(int w, int h): Super(w, h) {} float &operator()(int w, int h) { return (*this)[h][w]; } const float &operator()(int w, int h) const { return (*this)[h][w]; } float &operator()(int i) { return static_cast(*this)[i]; } const float &operator()(int i) const { return const_cast(*this).operator()(i); } int getRows() const { return const_cast(*this).height(); } int getCols() const { return const_cast(*this).width(); } float *data() { return static_cast(*this); } const float *data() const { return const_cast(*this).data(); } }; void downSample(const Array2Df& A, Array2Df& B) { const int width = B.getCols(); const int height = B.getRows(); // Note, I've uncommented all omp directives. They are all ok but are // applied to too small problems and in total don't lead to noticable // speed improvements. The main issue is the pde solver and in case of the // fft solver uses optimised threaded fftw routines. //#pragma omp parallel for for ( int y=0 ; ygetCols(); int height = H->getRows(); const int size = width*height; pyramids[0] = new Array2Df(width,height); //#pragma omp parallel for shared(pyramids, H) for( int i=0 ; i 2 && height > 2) { width /= 2; height /= 2; pyramids[k] = new Array2Df(width,height); downSample(*L, *pyramids[k]); } else { // RT - now nlevels is fixed in tmo_fattal02 (see the comment in // there), so it might happen that we have to add some padding to // the gaussian pyramids pyramids[k] = new Array2Df(width,height); for (int j = 0, n = width*height; j < n; ++j) { (*pyramids[k])(j) = (*L)(j); } } delete L; L = new Array2Df(width,height); gaussianBlur( *pyramids[k], *L ); } delete L; } //-------------------------------------------------------------------- float calculateGradients(Array2Df* H, Array2Df* G, int k) { const int width = H->getCols(); const int height = H->getRows(); const float divider = pow( 2.0f, k+1 ); float avgGrad = 0.0f; //#pragma omp parallel for shared(G,H) reduction(+:avgGrad) for( int y=0 ; y(x * 0.5f); //x / 2.f; int ay = static_cast(y * 0.5f); //y / 2.f; ax = (axgetCols(); // int height = A->getRows(); // int x,y; // for( y=0 ; ygetCols(); int height = gradients[nlevels-1]->getRows(); Array2Df** fi = new Array2Df*[nlevels]; fi[nlevels-1] = new Array2Df(width,height); if (newfattal) { //#pragma omp parallel for shared(fi) for ( int k = 0 ; k < width*height ; k++ ) { (*fi[nlevels-1])(k) = 1.0f; } } for ( int k = nlevels-1; k >= 0 ; k-- ) { width = gradients[k]->getCols(); height = gradients[k]->getRows(); // only apply gradients to levels>=detail_level but at least to the coarsest if ( k >= detail_level ||k==nlevels-1 || newfattal == false) { //DEBUG_STR << "calculateFiMatrix: apply gradient to level " << k << endl; //#pragma omp parallel for shared(fi,avgGrad) for ( int y = 0; y < height; y++ ) { for ( int x = 0; x < width; x++ ) { float grad = ((*gradients[k])(x,y) < 1e-4f) ? 1e-4 : (*gradients[k])(x,y); float a = alfa * avgGrad[k]; float value = powf((grad+noise)/a, beta - 1.0f); if (newfattal) (*fi[k])(x,y) *= value; else (*fi[k])(x,y) = value; } } } // create next level if ( k>1 ) { width = gradients[k-1]->getCols(); height = gradients[k-1]->getRows(); fi[k-1] = new Array2Df(width,height); } else fi[0] = FI; // highest level -> result if ( k>0 && newfattal ) { upSample(*fi[k], *fi[k-1]); // upsample to next level gaussianBlur(*fi[k-1], *fi[k-1]); } } for ( int k=1 ; k vI; std::copy(data, data + size, std::back_inserter(vI)); std::sort(vI.begin(), vI.end()); minLum = vI.at( int(minPrct*vI.size()) ); maxLum = vI.at( int(maxPrct*vI.size()) ); } void solve_pde_fft(Array2Df *F, Array2Df *U, bool multithread); void rescale_bilinear(const Array2Df &src, Array2Df &dst, bool multithread); // RT void tmo_fattal02(size_t width, size_t height, const Array2Df& Y, Array2Df& L, float alfa, float beta, float noise, int detail_level, bool multithread) { // #ifdef TIMER_PROFILING // msec_timer stop_watch; // stop_watch.start(); // #endif static const float black_point = 0.1f; static const float white_point = 0.5f; static const float gamma = 1.0f; // 0.8f; // static const int detail_level = 3; if ( detail_level < 0 ) detail_level = 0; if ( detail_level > 3 ) detail_level = 3; // ph.setValue(2); // if (ph.canceled()) return; /* RT -- we use a hardcoded value for nlevels, to limit the * dependency of the result on the image size. When using an auto computed * nlevels value, you would get vastly different results with different * image sizes, making it essentially impossible to preview the tool * inside RT. With a hardcoded value, the results for the preview are much * closer to those for the final image */ // int MSIZE = 32; // minimum size of gaussian pyramid // // I believe a smaller value than 32 results in slightly better overall // // quality but I'm only applying this if the newly implemented fft solver // // is used in order not to change behaviour of the old version // // TODO: best let the user decide this value // // if (fftsolver) // { // MSIZE = 8; // } int size = width*height; // unsigned int x,y; // int i, k; // find max & min values, normalize to range 0..100 and take logarithm float minLum = Y(0,0); float maxLum = Y(0,0); for ( int i=0 ; i maxLum ) ? Y(i) : maxLum; } Array2Df* H = new Array2Df(width, height); //#pragma omp parallel for private(i) shared(H, Y, maxLum) for ( int i=0 ; i RT_dimension_cap) { float s = float(RT_dimension_cap) / float(dim); Array2Df *HH = new Array2Df(width * s, height * s); rescale_bilinear(*H, *HH, multithread); fullH = H; H = HH; width = H->getCols(); height = H->getRows(); } /** RT */ // create gaussian pyramids // int mins = (width= MSIZE ) // { // nlevels++; // mins /= 2; // } // // std::cout << "DEBUG: nlevels = " << nlevels << ", mins = " << mins << std::endl; // // The following lines solves a bug with images particularly small // if (nlevels == 0) nlevels = 1; const int nlevels = 7; // RT -- see above Array2Df** pyramids = new Array2Df*[nlevels]; createGaussianPyramids(H, pyramids, nlevels); // ph.setValue(8); // calculate gradients and its average values on pyramid levels Array2Df** gradients = new Array2Df*[nlevels]; float* avgGrad = new float[nlevels]; for ( int k=0 ; kgetCols(), pyramids[k]->getRows()); avgGrad[k] = calculateGradients(pyramids[k],gradients[k], k); } // ph.setValue(12); // calculate fi matrix Array2Df* FI = new Array2Df(width, height); calculateFiMatrix(FI, gradients, avgGrad, nlevels, detail_level, alfa, beta, noise); // dumpPFS( "FI.pfs", FI, "Y" ); for ( int i=0 ; i= height ? height-2 : y+1); unsigned int xp1 = (x+1 >= width ? width-2 : x+1); // forward differences in H, so need to use between-points approx of FI (*Gx)(x,y) = ((*H)(xp1,y)-(*H)(x,y)) * 0.5*((*FI)(xp1,y)+(*FI)(x,y)); (*Gy)(x,y) = ((*H)(x,yp1)-(*H)(x,y)) * 0.5*((*FI)(x,yp1)+(*FI)(x,y)); } // else // for ( size_t y=0 ; y 0 ) DivG(x,y) -= (*Gx)(x-1,y); if ( y > 0 ) DivG(x,y) -= (*Gy)(x,y-1); // if (fftsolver) { if (x==0) DivG(x,y) += (*Gx)(x,y); if (y==0) DivG(x,y) += (*Gy)(x,y); } } } delete Gx; delete Gy; // ph.setValue(20); // if (ph.canceled()) // { // return; // } // dumpPFS( "DivG.pfs", DivG, "Y" ); // solve pde and exponentiate (ie recover compressed image) { Array2Df U(width, height); // if (fftsolver) { solve_pde_fft(&DivG, &U, multithread);//, ph); } // else // { // solve_pde_multigrid(&DivG, &U, ph); // } // #ifndef NDEBUG // printf("\npde residual error: %f\n", residual_pde(&U, &DivG)); // #endif // ph.setValue(90); // if ( ph.canceled() ) // { // return; // } for ( size_t idx = 0 ; idx < height*width; ++idx ) { L(idx) = expf( gamma * U(idx) ); } } // ph.setValue(95); // remove percentile of min and max values and renormalize float cut_min = 0.01f * black_point; float cut_max = 1.0f - 0.01f * white_point; assert(cut_min>=0.0f && (cut_max<=1.0f) && (cut_min 1.0 } // #ifdef TIMER_PROFILING // stop_watch.stop_and_update(); // cout << endl; // cout << "tmo_fattal02 = " << stop_watch.get_time() << " msec" << endl; // #endif // ph.setValue(96); } /** * * @file pde_fft.cpp * @brief Direct Poisson solver using the discrete cosine transform * * @author Tino Kluge (tino.kluge@hrz.tu-chemnitz.de) * */ ////////////////////////////////////////////////////////////////////// // Direct Poisson solver using the discrete cosine transform ////////////////////////////////////////////////////////////////////// // by Tino Kluge (tino.kluge@hrz.tu-chemnitz.de) // // let U and F be matrices of order (n1,n2), ie n1=height, n2=width // and L_x of order (n2,n2) and L_y of order (n1,n1) and both // representing the 1d Laplace operator with Neumann boundary conditions, // ie L_x and L_y are tridiagonal matrices of the form // // ( -2 2 ) // ( 1 -2 1 ) // ( . . . ) // ( 1 -2 1 ) // ( 2 -2 ) // // then this solver computes U given F based on the equation // // ------------------------- // L_y U + (L_x U^tr)^tr = F // ------------------------- // // Note, if the first and last row of L_x and L_y contained one's instead of // two's then this equation would be exactly the 2d Poisson equation with // Neumann boundary conditions. As a simple rule: // - Neumann: assume U(-1)=U(0) --> U(i-1) - 2 U(i) + U(i+1) becomes // i=0: U(0) - 2 U(0) + U(1) = -U(0) + U(1) // - our system: assume U(-1)=U(1) --> this becomes // i=0: U(1) - 2(0) + U(1) = -2 U(0) + 2 U(1) // // The multi grid solver solve_pde_multigrid() solves the 2d Poisson pde // with the right Neumann boundary conditions, U(-1)=U(0), see function // atimes(). This means the assembly of the right hand side F is different // for both solvers. // #include // #include // #include // #include // #include "arch/math.h" // #include // #ifdef _OPENMP // #include // #endif // #include // #include // #include "Libpfs/progress.h" // #include "Libpfs/array2d.h" // #include "pde.h" // using namespace std; // #ifndef SQR // #define SQR(x) (x)*(x) // #endif // returns T = EVy A EVx^tr // note, modifies input data void transform_ev2normal(Array2Df *A, Array2Df *T) { int width = A->getCols(); int height = A->getRows(); assert((int)T->getCols()==width && (int)T->getRows()==height); // the discrete cosine transform is not exactly the transform needed // need to scale input values to get the right transformation for(int y=1 ; ydata(), T->data(), FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE); fftwf_execute(p); fftwf_destroy_plan(p); } // returns T = EVy^-1 * A * (EVx^-1)^tr void transform_normal2ev(Array2Df *A, Array2Df *T) { int width = A->getCols(); int height = A->getRows(); assert((int)T->getCols()==width && (int)T->getRows()==height); // executes 2d discrete cosine transform fftwf_plan p; p=fftwf_plan_r2r_2d(height, width, A->data(), T->data(), FFTW_REDFT00, FFTW_REDFT00, FFTW_ESTIMATE); fftwf_execute(p); fftwf_destroy_plan(p); // need to scale the output matrix to get the right transform for(int y=0 ; y get_lambda(int n) { assert(n>1); std::vector v(n); for (int i=0; igetCols(); // int height = F->getRows(); // double sum=0.0; // for(int y=1 ; ygetCols(); int height = F->getRows(); assert((int)U->getCols()==width && (int)U->getRows()==height); // activate parallel execution of fft routines #ifdef RT_FFTW3F_OMP if (multithread) { fftwf_init_threads(); fftwf_plan_with_nthreads( omp_get_max_threads() ); } // #else // fftwf_plan_with_nthreads( 2 ); #endif // in general there might not be a solution to the Poisson pde // with Neumann boundary conditions unless the boundary satisfies // an integral condition, this function modifies the boundary so that // the condition is exactly satisfied // if(adjust_bound) // { // //DEBUG_STR << "solve_pde_fft: checking boundary conditions" << std::endl; // make_compatible_boundary(F); // } // transforms F into eigenvector space: Ftr = //DEBUG_STR << "solve_pde_fft: transform F to ev space (fft)" << std::endl; Array2Df* F_tr = new Array2Df(width,height); transform_normal2ev(F, F_tr); // TODO: F no longer needed so could release memory, but as it is an // input parameter we won't do that // ph.setValue(50); // if (ph.canceled()) // { // delete F_tr; // return; // } //DEBUG_STR << "solve_pde_fft: F_tr(0,0) = " << (*F_tr)(0,0); //DEBUG_STR << " (must be 0 for solution to exist)" << std::endl; // in the eigenvector space the solution is very simple //DEBUG_STR << "solve_pde_fft: solve in eigenvector space" << std::endl; Array2Df* U_tr = new Array2Df(width,height); std::vector l1=get_lambda(height); std::vector l2=get_lambda(width); for(int y=0 ; ygetCols(); // int height = U->getRows(); // assert((int)F->getCols()==width && (int)F->getRows()==height); // double res=0.0; // for(int y=1;y( sqrt(res) ); // } /***************************************************************************** * RT code from here on *****************************************************************************/ inline float get_bilinear_value(const Array2Df &src, float x, float y) { // Get integer and fractional parts of numbers int xi = x; int yi = y; float xf = x - xi; float yf = y - yi; int xi1 = std::min(xi+1, src.getCols()-1); int yi1 = std::min(yi+1, src.getRows()-1); float bl = src(xi, yi); float br = src(xi1, yi); float tl = src(xi, yi1); float tr = src(xi1, yi1); // interpolate float b = xf * br + (1.f - xf) * bl; float t = xf * tr + (1.f - xf) * tl; float pxf = yf * t + (1.f - yf) * b; return pxf; } void rescale_bilinear(const Array2Df &src, Array2Df &dst, bool multithread) { float col_scale = float(src.getCols())/float(dst.getCols()); float row_scale = float(src.getRows())/float(dst.getRows()); #ifdef _OPENMP #pragma omp parallel for if (multithread) #endif for (int x = 0; x < dst.getCols(); ++x) { for (int y = 0; y < dst.getRows(); ++y) { dst(x, y) = get_bilinear_value(src, x * col_scale, y * row_scale); } } } void tmo_fattal02_RT(Imagefloat *rgb, float alpha, float beta, int detail_level, bool multiThread) { // sanity check if (alpha <= 0 || beta <= 0) { return; } int w = rgb->getWidth(); int h = rgb->getHeight(); Array2Df Yr(w, h); Array2Df L(w, h); const float epsilon = 1e-4f; #ifdef _OPENMP #pragma omp parallel for if (multiThread) #endif for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { Yr(x, y) = std::max(Color::rgbLuminance(rgb->r(y, x), rgb->g(y, x), rgb->b(y, x)), epsilon); // clip really black pixels, otherwise it doesn't work at all (not sure why...) } } float noise = alpha * 0.01f; if (settings->verbose) { std::cout << "ToneMapFattal02: alpha = " << alpha << ", beta = " << beta << ", detail_level = " << detail_level << std::endl; } tmo_fattal02(w, h, Yr, L, alpha, beta, noise, detail_level, multiThread); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { float Y = Yr(x, y); float l = std::max(L(x, y), epsilon); rgb->r(y, x) = std::max(rgb->r(y, x)/Y, 0.f) * l; rgb->g(y, x) = std::max(rgb->g(y, x)/Y, 0.f) * l; rgb->b(y, x) = std::max(rgb->b(y, x)/Y, 0.f) * l; } } rgb->normalizeFloatTo65535(); } } // namespace void ImProcFunctions::ToneMapFattal02(Imagefloat *rgb) { const int detail_level = 3; tmo_fattal02_RT(rgb, params->fattal.alpha, params->fattal.beta, detail_level, multiThread); } } // namespace rtengine