diff --git a/CMakeLists.txt b/CMakeLists.txt index a0571e9cf..d3e2f2c4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,6 +311,7 @@ if(WITH_SYSTEM_KLT) find_package(KLT REQUIRED) endif() + # Check for libcanberra-gtk3 (sound events on Linux): if(UNIX AND(NOT APPLE)) pkg_check_modules(CANBERRA-GTK REQUIRED libcanberra-gtk3) @@ -345,6 +346,35 @@ if(OPTION_OMP) endif() endif() +# check for libfftw3f_omp +include(CheckCSourceCompiles) +if(OPENMP_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${FFTW3F_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES) + foreach(l ${FFTW3F_LIBRARIES}) + find_library(_f ${l} PATHS ${FFTW3F_LIBRARY_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${_f}) + endforeach() + check_c_source_compiles( +"#include +int main() +{ + fftwf_init_threads(); + fftwf_plan_with_nthreads(1); + return 0; +}" _fftw3f_multithread) + if(_fftw3f_multithread) + add_definitions(-DRT_FFTW3F_OMP) + else() + find_library(fftw3f_omp fftw3f_omp PATHS ${FFTW3F_LIBRARY_DIRS}) + if(fftw3f_omp) + add_definitions(-DRT_FFTW3F_OMP) + set(FFTW3F_LIBRARIES ${FFTW3F_LIBRARIES} ${fftw3f_omp}) + endif() + endif() +endif() + + # Find out whether we are building out of source: get_filename_component(ABS_SOURCE_DIR "${PROJECT_SOURCE_DIR}" ABSOLUTE) get_filename_component(ABS_BINARY_DIR "${CMAKE_BINARY_DIR}" ABSOLUTE) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index c39d9f5e5..e90a61b41 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -703,6 +703,9 @@ HISTORY_MSG_472;PS - Adoucir les transitions HISTORY_MSG_473;PS - Utiliser LMMSE HISTORY_MSG_474;PS - Égaliser HISTORY_MSG_475;PS - Égaliser par canal +HISTORY_MSG_488;Compression tonale HDR +HISTORY_MSG_489;CT HDR - Seuil +HISTORY_MSG_490;CT HDR - Quantité HISTORY_NEWSNAPSHOT;Ajouter HISTORY_NEWSNAPSHOT_TOOLTIP;Raccourci: Alt-s HISTORY_SNAPSHOT;Capture @@ -904,6 +907,7 @@ PARTIALPASTE_SHADOWSHIGHLIGHTS;Ombres/Hautes lumières PARTIALPASTE_SHARPENEDGE;Bords PARTIALPASTE_SHARPENING;Netteté PARTIALPASTE_SHARPENMICRO;Microcontraste +PARTIALPASTE_TM_FATTAL;Compression tonale HDR (Fattal02) PARTIALPASTE_VIBRANCE;Vibrance PARTIALPASTE_VIGNETTING;Correction du vignettage PARTIALPASTE_WAVELETGROUP;Niveaux d'ondelette @@ -1892,6 +1896,9 @@ TP_SHARPENMICRO_AMOUNT;Quantité TP_SHARPENMICRO_LABEL;Microcontraste TP_SHARPENMICRO_MATRIX;Matrice 3×3 au lieu de 5×5 TP_SHARPENMICRO_UNIFORMITY;Uniformité +TP_TM_FATTAL_LABEL;Compression Tonale HDR (Fattal02) +TP_TM_FATTAL_THRESHOLD;Seuil +TP_TM_FATTAL_AMOUNT;Quantité TP_VIBRANCE_AVOIDCOLORSHIFT;Éviter les dérives de teinte TP_VIBRANCE_CURVEEDITOR_SKINTONES;TT TP_VIBRANCE_CURVEEDITOR_SKINTONES_LABEL;Tons chair diff --git a/rtdata/languages/default b/rtdata/languages/default index 84037a6a6..8320a351b 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -719,6 +719,9 @@ HISTORY_MSG_484;CAM02 - Auto Yb scene HISTORY_MSG_485;Lens Correction HISTORY_MSG_486;Lens Correction - Camera HISTORY_MSG_487;Lens Correction - Lens +HISTORY_MSG_488;HDR Tone Mapping +HISTORY_MSG_489;HDR TM - Threshold +HISTORY_MSG_490;HDR TM - Amount HISTORY_NEWSNAPSHOT;Add HISTORY_NEWSNAPSHOT_TOOLTIP;Shortcut: Alt-s HISTORY_SNAPSHOT;Snapshot @@ -925,6 +928,7 @@ PARTIALPASTE_SHADOWSHIGHLIGHTS;Shadows/highlights PARTIALPASTE_SHARPENEDGE;Edges PARTIALPASTE_SHARPENING;Sharpening (USM/RL) PARTIALPASTE_SHARPENMICRO;Microcontrast +PARTIALPASTE_TM_FATTAL;HDR Tone mapping PARTIALPASTE_VIBRANCE;Vibrance PARTIALPASTE_VIGNETTING;Vignetting correction PARTIALPASTE_WAVELETGROUP;Wavelet Levels @@ -1924,6 +1928,9 @@ TP_SHARPENMICRO_AMOUNT;Quantity TP_SHARPENMICRO_LABEL;Microcontrast TP_SHARPENMICRO_MATRIX;3×3 matrix instead of 5×5 TP_SHARPENMICRO_UNIFORMITY;Uniformity +TP_TM_FATTAL_LABEL;HDR Tone Mapping +TP_TM_FATTAL_THRESHOLD;Threshold +TP_TM_FATTAL_AMOUNT;Amount TP_VIBRANCE_AVOIDCOLORSHIFT;Avoid color shift TP_VIBRANCE_CURVEEDITOR_SKINTONES;HH TP_VIBRANCE_CURVEEDITOR_SKINTONES_LABEL;Skin-tones diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 0b003909f..53b374388 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -113,6 +113,7 @@ set(RTENGINESOURCEFILES stdimagesource.cc utils.cc rtlensfun.cc + tmo_fattal02.cc ) if(LENSFUN_HAS_LOAD_DIRECTORY) diff --git a/rtengine/FTblockDN.cc b/rtengine/FTblockDN.cc index bf79f5445..0634deedd 100644 --- a/rtengine/FTblockDN.cc +++ b/rtengine/FTblockDN.cc @@ -71,9 +71,16 @@ namespace rtengine extern const Settings* settings; +extern MyMutex *fftwMutex; -void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, const int height, const Median medianType, const int iterations, const int numThreads, float **buffer) + +namespace { + +template +void do_median_denoise(float **src, float **dst, float upperBound, const int width, const int height, const ImProcFunctions::Median medianType, const int iterations, const int numThreads, float **buffer) { + typedef ImProcFunctions::Median Median; + int border = 1; switch (medianType) { @@ -110,7 +117,7 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, // we need a buffer if src == dst or if (src != dst && iterations > 1) if (src == dst || iterations > 1) { - if (buffer == nullptr) { // we didn't get a buufer => create one + if (buffer == nullptr) { // we didn't get a buffer => create one allocBuffer = new float*[height]; for (int i = 0; i < height; ++i) { @@ -154,13 +161,17 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, switch (medianType) { case Median::TYPE_3X3_SOFT: { for (; j < width - border; ++j) { - medianOut[i][j] = median( - medianIn[i - 1][j], - medianIn[i][j - 1], - medianIn[i][j], - medianIn[i][j + 1], - medianIn[i + 1][j] - ); + if (!useUpperBound || medianIn[i][j] <= upperBound) { + medianOut[i][j] = median( + medianIn[i - 1][j], + medianIn[i][j - 1], + medianIn[i][j], + medianIn[i][j + 1], + medianIn[i + 1][j] + ); + } else { + medianOut[i][j] = medianIn[i][j]; + } } break; @@ -168,17 +179,21 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, case Median::TYPE_3X3_STRONG: { for (; j < width - border; ++j) { - medianOut[i][j] = median( - medianIn[i - 1][j - 1], - medianIn[i - 1][j], - medianIn[i - 1][j + 1], - medianIn[i][j - 1], - medianIn[i][j], - medianIn[i][j + 1], - medianIn[i + 1][j - 1], - medianIn[i + 1][j], - medianIn[i + 1][j + 1] - ); + if (!useUpperBound || medianIn[i][j] <= upperBound) { + medianOut[i][j] = median( + medianIn[i - 1][j - 1], + medianIn[i - 1][j], + medianIn[i - 1][j + 1], + medianIn[i][j - 1], + medianIn[i][j], + medianIn[i][j + 1], + medianIn[i + 1][j - 1], + medianIn[i + 1][j], + medianIn[i + 1][j + 1] + ); + } else { + medianOut[i][j] = medianIn[i][j]; + } } break; @@ -186,21 +201,25 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, case Median::TYPE_5X5_SOFT: { for (; j < width - border; ++j) { - medianOut[i][j] = median( - medianIn[i - 2][j], - medianIn[i - 1][j - 1], - medianIn[i - 1][j], - medianIn[i - 1][j + 1], - medianIn[i][j - 2], - medianIn[i][j - 1], - medianIn[i][j], - medianIn[i][j + 1], - medianIn[i][j + 2], - medianIn[i + 1][j - 1], - medianIn[i + 1][j], - medianIn[i + 1][j + 1], - medianIn[i + 2][j] - ); + if (!useUpperBound || medianIn[i][j] <= upperBound) { + medianOut[i][j] = median( + medianIn[i - 2][j], + medianIn[i - 1][j - 1], + medianIn[i - 1][j], + medianIn[i - 1][j + 1], + medianIn[i][j - 2], + medianIn[i][j - 1], + medianIn[i][j], + medianIn[i][j + 1], + medianIn[i][j + 2], + medianIn[i + 1][j - 1], + medianIn[i + 1][j], + medianIn[i + 1][j + 1], + medianIn[i + 2][j] + ); + } else { + medianOut[i][j] = medianIn[i][j]; + } } break; @@ -208,8 +227,7 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, case Median::TYPE_5X5_STRONG: { #ifdef __SSE2__ - - for (; j < width - border - 3; j += 4) { + for (; !useUpperBound && j < width - border - 3; j += 4) { STVFU( medianOut[i][j], median( @@ -243,35 +261,38 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, } #endif - for (; j < width - border; ++j) { - medianOut[i][j] = median( - medianIn[i - 2][j - 2], - medianIn[i - 2][j - 1], - medianIn[i - 2][j], - medianIn[i - 2][j + 1], - medianIn[i - 2][j + 2], - medianIn[i - 1][j - 2], - medianIn[i - 1][j - 1], - medianIn[i - 1][j], - medianIn[i - 1][j + 1], - medianIn[i - 1][j + 2], - medianIn[i][j - 2], - medianIn[i][j - 1], - medianIn[i][j], - medianIn[i][j + 1], - medianIn[i][j + 2], - medianIn[i + 1][j - 2], - medianIn[i + 1][j - 1], - medianIn[i + 1][j], - medianIn[i + 1][j + 1], - medianIn[i + 1][j + 2], - medianIn[i + 2][j - 2], - medianIn[i + 2][j - 1], - medianIn[i + 2][j], - medianIn[i + 2][j + 1], - medianIn[i + 2][j + 2] - ); + if (!useUpperBound || medianIn[i][j] <= upperBound) { + medianOut[i][j] = median( + medianIn[i - 2][j - 2], + medianIn[i - 2][j - 1], + medianIn[i - 2][j], + medianIn[i - 2][j + 1], + medianIn[i - 2][j + 2], + medianIn[i - 1][j - 2], + medianIn[i - 1][j - 1], + medianIn[i - 1][j], + medianIn[i - 1][j + 1], + medianIn[i - 1][j + 2], + medianIn[i][j - 2], + medianIn[i][j - 1], + medianIn[i][j], + medianIn[i][j + 1], + medianIn[i][j + 2], + medianIn[i + 1][j - 2], + medianIn[i + 1][j - 1], + medianIn[i + 1][j], + medianIn[i + 1][j + 1], + medianIn[i + 1][j + 2], + medianIn[i + 2][j - 2], + medianIn[i + 2][j - 1], + medianIn[i + 2][j], + medianIn[i + 2][j + 1], + medianIn[i + 2][j + 2] + ); + } else { + medianOut[i][j] = medianIn[i][j]; + } } break; @@ -281,7 +302,7 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, #ifdef __SSE2__ std::array vpp ALIGNED16; - for (; j < width - border - 3; j += 4) { + for (; !useUpperBound && j < width - border - 3; j += 4) { for (int kk = 0, ii = -border; ii <= border; ++ii) { for (int jj = -border; jj <= border; ++jj, ++kk) { vpp[kk] = LVFU(medianIn[i + ii][j + jj]); @@ -294,15 +315,19 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, #endif std::array pp; - + for (; j < width - border; ++j) { - for (int kk = 0, ii = -border; ii <= border; ++ii) { - for (int jj = -border; jj <= border; ++jj, ++kk) { - pp[kk] = medianIn[i + ii][j + jj]; + if (!useUpperBound || medianIn[i][j] <= upperBound) { + for (int kk = 0, ii = -border; ii <= border; ++ii) { + for (int jj = -border; jj <= border; ++jj, ++kk) { + pp[kk] = medianIn[i + ii][j + jj]; + } } + + medianOut[i][j] = median(pp); + } else { + medianOut[i][j] = medianIn[i][j]; } - - medianOut[i][j] = median(pp); } break; @@ -312,7 +337,7 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, #ifdef __SSE2__ std::array vpp ALIGNED16; - for (; j < width - border - 3; j += 4) { + for (; !useUpperBound && j < width - border - 3; j += 4) { for (int kk = 0, ii = -border; ii <= border; ++ii) { for (int jj = -border; jj <= border; ++jj, ++kk) { vpp[kk] = LVFU(medianIn[i + ii][j + jj]); @@ -325,15 +350,19 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, #endif std::array pp; - + for (; j < width - border; ++j) { - for (int kk = 0, ii = -border; ii <= border; ++ii) { - for (int jj = -border; jj <= border; ++jj, ++kk) { - pp[kk] = medianIn[i + ii][j + jj]; + if (!useUpperBound || medianIn[i][j] <= upperBound) { + for (int kk = 0, ii = -border; ii <= border; ++ii) { + for (int jj = -border; jj <= border; ++jj, ++kk) { + pp[kk] = medianIn[i + ii][j + jj]; + } } + + medianOut[i][j] = median(pp); + } else { + medianOut[i][j] = medianIn[i][j]; } - - medianOut[i][j] = median(pp); } for (; j < width; ++j) { @@ -364,9 +393,8 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, #ifdef _OPENMP #pragma omp parallel for num_threads(numThreads) if (numThreads>1) #endif - - for (int i = border; i < height - border; ++i) { - for (int j = border; j < width - border; ++j) { + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { dst[i][j] = medianOut[i][j]; } } @@ -381,6 +409,21 @@ void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, } } +} // namespace + + +void ImProcFunctions::Median_Denoise(float **src, float **dst, const int width, const int height, const Median medianType, const int iterations, const int numThreads, float **buffer) +{ + do_median_denoise(src, dst, 0.f, width, height, medianType, iterations, numThreads, buffer); +} + + +void ImProcFunctions::Median_Denoise(float **src, float **dst, float upperBound, const int width, const int height, const Median medianType, const int iterations, const int numThreads, float **buffer) +{ + do_median_denoise(src, dst, upperBound, width, height, medianType, iterations, numThreads, buffer); +} + + void ImProcFunctions::Tile_calc(int tilesize, int overlap, int kall, int imwidth, int imheight, int &numtiles_W, int &numtiles_H, int &tilewidth, int &tileheight, int &tileWskip, int &tileHskip) { @@ -445,8 +488,7 @@ SSEFUNCTION void ImProcFunctions::RGB_denoise(int kall, Imagefloat * src, Imagef return; } - static MyMutex FftwMutex; - MyMutex::MyLock lock(FftwMutex); + MyMutex::MyLock lock(*fftwMutex); const nrquality nrQuality = (dnparams.smethod == "shal") ? QUALITY_STANDARD : QUALITY_HIGH;//shrink method const float qhighFactor = (nrQuality == QUALITY_HIGH) ? 1.f / static_cast( settings->nrhigh) : 1.0f; diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 19cb654ec..88397d6ee 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -168,7 +168,7 @@ void Crop::update (int todo) bool needstransform = parent->ipf.needsTransform(); - if (todo & (M_INIT | M_LINDENOISE)) { + if (todo & (M_INIT | M_LINDENOISE | M_HDR)) { MyMutex::MyLock lock (parent->minit); // Also used in improccoord int tr = getCoarseBitMask (params.coarse); @@ -690,6 +690,79 @@ void Crop::update (int todo) // has to be called after setCropSizes! Tools prior to this point can't handle the Edit mechanism, but that shouldn't be a problem. createBuffer (cropw, croph); + std::unique_ptr fattalCrop; + if ((todo & M_HDR) && params.fattal.enabled) { + Imagefloat *f = origCrop; + int fw = skips(parent->fw, skip); + int fh = skips(parent->fh, skip); + bool need_cropping = false; + bool need_fattal = true; + + if (trafx || trafy || trafw != fw || trafh != fh) { + need_cropping = true; + // fattal needs to work on the full image. So here we get the full + // image from imgsrc, and replace the denoised crop in case + if (!params.dirpyrDenoise.enabled && skip == 1 && parent->fattal_11_dcrop_cache) { + f = parent->fattal_11_dcrop_cache; + need_fattal = false; + } else { + f = new Imagefloat(fw, fh); + fattalCrop.reset(f); + PreviewProps pp (0, 0, parent->fw, parent->fh, skip); + int tr = getCoarseBitMask(params.coarse); + parent->imgsrc->getImage(parent->currWB, tr, f, pp, params.toneCurve, params.raw); + parent->imgsrc->convertColorSpace(f, params.icm, parent->currWB); + + if (params.dirpyrDenoise.enabled) { + // copy the denoised crop + int oy = trafy / skip; + int ox = trafx / skip; +#ifdef _OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < baseCrop->getHeight(); ++y) { + int dy = oy + y; + for (int x = 0; x < baseCrop->getWidth(); ++x) { + int dx = ox + x; + f->r(dy, dx) = baseCrop->r(y, x); + f->g(dy, dx) = baseCrop->g(y, x); + f->b(dy, dx) = baseCrop->b(y, x); + } + } + } else if (skip == 1) { + parent->fattal_11_dcrop_cache = f; // cache this globally + fattalCrop.release(); + } + } + } + if (need_fattal) { + parent->ipf.ToneMapFattal02(f); + } + + // crop back to the size expected by the rest of the pipeline + if (need_cropping) { + Imagefloat *c = origCrop; + + int oy = trafy / skip; + int ox = trafx / skip; +#ifdef _OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < trafh; ++y) { + int cy = y + oy; + for (int x = 0; x < trafw; ++x) { + int cx = x + ox; + c->r(y, x) = f->r(cy, cx); + c->g(y, x) = f->g(cy, cx); + c->b(y, x) = f->b(cy, cx); + } + } + baseCrop = c; + } else { + baseCrop = f; + } + } + // transform if (needstransform || ((todo & (M_TRANSFORM | M_RGBCURVE)) && params.dirpyrequalizer.cbdlMethod == "bef" && params.dirpyrequalizer.enabled && !params.colorappearance.enabled)) { if (!transCrop) { @@ -1137,6 +1210,7 @@ bool Crop::setCropSizes (int rcx, int rcy, int rcw, int rch, int skip, bool inte ory = by1; orw = bw; orh = bh; + ProcParams& params = parent->params; parent->ipf.transCoord (parent->fw, parent->fh, bx1, by1, bw, bh, orx, ory, orw, orh); @@ -1176,6 +1250,8 @@ bool Crop::setCropSizes (int rcx, int rcy, int rcw, int rch, int skip, bool inte orh = min (y2 - y1, parent->fh - ory); } + leftBorder = skips (rqx1 - bx1, skip); + upperBorder = skips (rqy1 - by1, skip); PreviewProps cp (orx, ory, orw, orh, skip); int orW, orH; @@ -1187,9 +1263,6 @@ bool Crop::setCropSizes (int rcx, int rcy, int rcw, int rch, int skip, bool inte int cw = skips (bw, skip); int ch = skips (bh, skip); - leftBorder = skips (rqx1 - bx1, skip); - upperBorder = skips (rqy1 - by1, skip); - if (settings->verbose) { printf ("setsizes starts (%d, %d, %d, %d, %d, %d)\n", orW, orH, trafw, trafh, cw, ch); } diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index ccf517329..96710ee63 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -33,7 +33,7 @@ namespace rtengine extern const Settings* settings; ImProcCoordinator::ImProcCoordinator () - : orig_prev (nullptr), oprevi (nullptr), oprevl (nullptr), nprevl (nullptr), previmg (nullptr), workimg (nullptr), + : orig_prev (nullptr), oprevi (nullptr), oprevl (nullptr), nprevl (nullptr), fattal_11_dcrop_cache(nullptr), previmg (nullptr), workimg (nullptr), ncie (nullptr), imgsrc (nullptr), shmap (nullptr), lastAwbEqual (0.), lastAwbTempBias (0.0), ipf (¶ms, true), monitorIntent (RI_RELATIVE), softProof (false), gamutCheck (false), scale (10), highDetailPreprocessComputed (false), highDetailRawComputed (false), allocated (false), bwAutoR (-9000.f), bwAutoG (-9000.f), bwAutoB (-9000.f), CAMMean (NAN), @@ -111,6 +111,10 @@ ImProcCoordinator::~ImProcCoordinator () mProcessing.lock(); mProcessing.unlock(); freeAll (); + if (fattal_11_dcrop_cache) { + delete fattal_11_dcrop_cache; + fattal_11_dcrop_cache = nullptr; + } std::vector toDel = crops; @@ -280,7 +284,7 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) } } - if (todo & (M_INIT | M_LINDENOISE)) { + if (todo & (M_INIT | M_LINDENOISE | M_HDR)) { MyMutex::MyLock initLock (minit); // Also used in crop window imgsrc->HLRecovery_Global ( params.toneCurve); // this handles Color HLRecovery @@ -385,25 +389,32 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) readyphase++; + if ((todo & M_HDR) && params.fattal.enabled) { + if (fattal_11_dcrop_cache) { + delete fattal_11_dcrop_cache; + fattal_11_dcrop_cache = nullptr; + } + ipf.ToneMapFattal02(orig_prev); + if (oprevi != orig_prev) { + delete oprevi; + } + } + oprevi = orig_prev; + progress ("Rotate / Distortion...", 100 * readyphase / numofphases); // Remove transformation if unneeded bool needstransform = ipf.needsTransform(); - - if (!needstransform && ! ((todo & (M_TRANSFORM | M_RGBCURVE)) && params.dirpyrequalizer.cbdlMethod == "bef" && params.dirpyrequalizer.enabled && !params.colorappearance.enabled) && orig_prev != oprevi) { - delete oprevi; - oprevi = orig_prev; - } - + if ((needstransform || ((todo & (M_TRANSFORM | M_RGBCURVE)) && params.dirpyrequalizer.cbdlMethod == "bef" && params.dirpyrequalizer.enabled && !params.colorappearance.enabled)) ) { - if (!oprevi || oprevi == orig_prev) { - oprevi = new Imagefloat (pW, pH); - } + assert(oprevi); + Imagefloat *op = oprevi; + oprevi = new Imagefloat (pW, pH); if (needstransform) - ipf.transform (orig_prev, oprevi, 0, 0, 0, 0, pW, pH, fw, fh, + ipf.transform (op, oprevi, 0, 0, 0, 0, pW, pH, fw, fh, imgsrc->getMetaData(), imgsrc->getRotateDegree(), false); else { - orig_prev->copyData (oprevi); + op->copyData (oprevi); } } diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index 41b901e93..2f5fe52e5 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -57,6 +57,7 @@ protected: Imagefloat *oprevi; LabImage *oprevl; LabImage *nprevl; + Imagefloat *fattal_11_dcrop_cache; // global cache for ToneMapFattal02 used in 1:1 detail windows (except when denoise is active) Image8 *previmg; // displayed image in monitor color space, showing the output profile as well (soft-proofing enabled, which then correspond to workimg) or not Image8 *workimg; // internal image in output color space for analysis CieImage *ncie; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 09a9ccd4e..08c3fa4cc 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -301,6 +301,7 @@ public: void Median_Denoise ( float **src, float **dst, int width, int height, Median medianType, int iterations, int numThreads, float **buffer = nullptr); + void Median_Denoise ( float **src, float **dst, float upperBound, int width, int height, Median medianType, int iterations, int numThreads, float **buffer = nullptr); void RGB_denoise (int kall, Imagefloat * src, Imagefloat * dst, Imagefloat * calclum, float * ch_M, float *max_r, float *max_b, bool isRAW, const procparams::DirPyrDenoiseParams & dnparams, const double expcomp, const NoiseCurve & noiseLCurve, const NoiseCurve & noiseCCurve, float &nresi, float &highresi); void RGB_denoise_infoGamCurve (const procparams::DirPyrDenoiseParams & dnparams, const bool isRAW, LUTf &gamcurve, float &gam, float &gamthresh, float &gamslope); void RGB_denoise_info (Imagefloat * src, Imagefloat * provicalc, bool isRAW, LUTf &gamcurve, float gam, float gamthresh, float gamslope, const procparams::DirPyrDenoiseParams & dnparams, const double expcomp, float &chaut, int &Nb, float &redaut, float &blueaut, float &maxredaut, float & maxblueaut, float &minredaut, float & minblueaut, float &chromina, float &sigma, float &lumema, float &sigma_L, float &redyel, float &skinc, float &nsknc, bool multiThread = false); @@ -343,6 +344,8 @@ public: void Badpixelscam (CieImage * src, CieImage * dst, double radius, int thresh, int mode, float skinprot, float chrom, int hotbad); void BadpixelsLab (LabImage * src, LabImage * dst, double radius, int thresh, int mode, float skinprot, float chrom); + void ToneMapFattal02(Imagefloat *rgb); + Image8* lab2rgb (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm); Image16* lab2rgb16 (LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm, GammaValues *ga = nullptr); // CieImage *ciec; diff --git a/rtengine/init.cc b/rtengine/init.cc index 6c1f4b98a..8d2cf9174 100644 --- a/rtengine/init.cc +++ b/rtengine/init.cc @@ -38,6 +38,7 @@ namespace rtengine const Settings* settings; MyMutex* lcmsMutex = nullptr; +MyMutex *fftwMutex = nullptr; int init (const Settings* s, Glib::ustring baseDir, Glib::ustring userSettingsDir, bool loadAll) { @@ -101,6 +102,7 @@ int init (const Settings* s, Glib::ustring baseDir, Glib::ustring userSettingsDi Color::init (); delete lcmsMutex; lcmsMutex = new MyMutex; + fftwMutex = new MyMutex; return 0; } diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 5f30374ee..3aa5505b5 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -515,6 +515,10 @@ enum ProcEvent { EvLensCorrMode = 484, EvLensCorrLensfunCamera = 485, EvLensCorrLensfunLens = 486, + // Fattal tone mapping + EvTMFattalEnabled = 487, + EvTMFattalThreshold = 488, + EvTMFattalAmount = 489, NUMOFEVENTS diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 1c29f3e4b..0ada68a62 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1241,6 +1241,8 @@ void ProcParams::setDefaults () epd.scale = 1.0; epd.reweightingIterates = 0; + fattal.setDefaults(); + sh.enabled = false; sh.hq = false; sh.highlights = 0; @@ -2442,6 +2444,19 @@ int ProcParams::save (const Glib::ustring &fname, const Glib::ustring &fname2, b keyFile.set_integer ("EPD", "ReweightingIterates", epd.reweightingIterates); } +// save fattal + if (!pedited || pedited->fattal.enabled) { + keyFile.set_boolean ("FattalToneMapping", "Enabled", fattal.enabled); + } + + if (!pedited || pedited->fattal.threshold) { + keyFile.set_integer ("FattalToneMapping", "Threshold", fattal.threshold); + } + + if (!pedited || pedited->fattal.amount) { + keyFile.set_integer ("FattalToneMapping", "Amount", fattal.amount); + } + /* // save lumaDenoise if (!pedited || pedited->lumaDenoise.enabled) keyFile.set_boolean ("Luminance Denoising", "Enabled", lumaDenoise.enabled); @@ -5588,6 +5603,33 @@ int ProcParams::load (const Glib::ustring &fname, ParamsEdited* pedited) } } +//Load FattalToneMapping + if (keyFile.has_group ("FattalToneMapping")) { + if (keyFile.has_key ("FattalToneMapping", "Enabled")) { + fattal.enabled = keyFile.get_boolean ("FattalToneMapping", "Enabled"); + + if (pedited) { + pedited->fattal.enabled = true; + } + } + + if (keyFile.has_key ("FattalToneMapping", "Threshold")) { + fattal.threshold = keyFile.get_double ("FattalToneMapping", "Threshold"); + + if (pedited) { + pedited->fattal.threshold = true; + } + } + + if (keyFile.has_key ("FattalToneMapping", "Amount")) { + fattal.amount = keyFile.get_double ("FattalToneMapping", "Amount"); + + if (pedited) { + pedited->fattal.amount = true; + } + } + } + // load lumaDenoise /*if (keyFile.has_group ("Luminance Denoising")) { if (keyFile.has_key ("Luminance Denoising", "Enabled")) { lumaDenoise.enabled = keyFile.get_boolean ("Luminance Denoising", "Enabled"); if (pedited) pedited->lumaDenoise.enabled = true; } @@ -8446,6 +8488,9 @@ bool ProcParams::operator== (const ProcParams& other) && epd.edgeStopping == other.epd.edgeStopping && epd.scale == other.epd.scale && epd.reweightingIterates == other.epd.reweightingIterates + && fattal.enabled == other.fattal.enabled + && fattal.threshold == other.fattal.threshold + && fattal.amount == other.fattal.amount && defringe.enabled == other.defringe.enabled && defringe.radius == other.defringe.radius && defringe.threshold == other.defringe.threshold diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 616e75537..b3139cdcb 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -738,6 +738,27 @@ public: int reweightingIterates; }; + +// Fattal02 Tone-Mapping parameters +class FattalToneMappingParams { +public: + bool enabled; + int threshold; + int amount; + + FattalToneMappingParams() + { + setDefaults(); + } + + void setDefaults() + { + enabled = false; + threshold = 0; + amount = 0; + } +}; + /** * Parameters of the shadow/highlight enhancement */ @@ -1384,6 +1405,7 @@ public: ImpulseDenoiseParams impulseDenoise; ///< Impulse denoising parameters DirPyrDenoiseParams dirpyrDenoise; ///< Directional Pyramid denoising parameters EPDParams epd; ///< Edge Preserving Decomposition parameters + FattalToneMappingParams fattal; ///< Fattal02 tone mapping SHParams sh; ///< Shadow/highlight enhancement parameters CropParams crop; ///< Crop parameters CoarseTransformParams coarse; ///< Coarse transformation (90, 180, 270 deg rotation, h/v flipping) parameters diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 74eda6110..c95b53c0a 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -513,7 +513,10 @@ int refreshmap[rtengine::NUMOFEVENTS] = { LUMINANCECURVE, // EvCATAutoyb DARKFRAME, // EvLensCorrMode DARKFRAME, // EvLensCorrLensfunCamera - DARKFRAME // EvLensCorrLensfunLens + DARKFRAME, // EvLensCorrLensfunLens + ALLNORAW, // EvTMFattalEnabled + HDR, // EvTMFattalThreshold + HDR // EvTMFattalAmount }; diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h index e262c9394..cea6b3c8e 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -20,22 +20,23 @@ #define __REFRESHMAP__ // Use M_VOID if you wish to update the proc params without updating the preview at all ! -#define M_VOID (1<<16) +#define M_VOID (1<<17) // Use M_MINUPDATE if you wish to update the preview without modifying the image (think about it like a "refreshPreview") // Must NOT be used with other event (i.e. will be used for MINUPDATE only) -#define M_MINUPDATE (1<<15) +#define M_MINUPDATE (1<<16) // Force high quality -#define M_HIGHQUAL (1<<14) +#define M_HIGHQUAL (1<<15) // Elementary functions that can be done to // the preview image when an event occurs -#define M_MONITOR (1<<13) -#define M_RETINEX (1<<12) -#define M_CROP (1<<11) -#define M_PREPROC (1<<10) -#define M_RAW (1<<9) -#define M_INIT (1<<8) -#define M_LINDENOISE (1<<7) +#define M_MONITOR (1<<14) +#define M_RETINEX (1<<13) +#define M_CROP (1<<12) +#define M_PREPROC (1<<11) +#define M_RAW (1<<10) +#define M_INIT (1<<9) +#define M_LINDENOISE (1<<8) +#define M_HDR (1<<7) #define M_TRANSFORM (1<<6) #define M_BLURMAP (1<<5) #define M_AUTOEXP (1<<4) @@ -46,21 +47,22 @@ // Bitfield of functions to do to the preview image when an event occurs // Use those or create new ones for your new events -#define FIRST (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_MONITOR) // without HIGHQUAL -#define ALL (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL -#define DARKFRAME (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define ALLNORAW (M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define TRANSFORM (M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define AUTOEXP (M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define RGBCURVE (M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define LUMINANCECURVE (M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define SHARPENING (M_LUMINANCE|M_COLOR) -#define IMPULSEDENOISE (M_LUMINANCE|M_COLOR) -#define DEFRINGE (M_LUMINANCE|M_COLOR) -#define DIRPYRDENOISE (M_LUMINANCE|M_COLOR) -#define DIRPYREQUALIZER (M_LUMINANCE|M_COLOR) +#define FIRST (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_MONITOR) // without HIGHQUAL +#define ALL (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL +#define DARKFRAME (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define ALLNORAW (M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define HDR (M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define TRANSFORM (M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define AUTOEXP (M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define RGBCURVE (M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define LUMINANCECURVE (M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define SHARPENING (M_LUMINANCE|M_COLOR) +#define IMPULSEDENOISE (M_LUMINANCE|M_COLOR) +#define DEFRINGE (M_LUMINANCE|M_COLOR) +#define DIRPYRDENOISE (M_LUMINANCE|M_COLOR) +#define DIRPYREQUALIZER (M_LUMINANCE|M_COLOR) #define GAMMA M_MONITOR #define CROP M_CROP #define RESIZE M_VOID diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 5572984f9..8adab4ec5 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -1046,6 +1046,10 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT ipf.firstAnalysis (baseImg, params, hist16); + if (params.fattal.enabled) { + ipf.ToneMapFattal02(baseImg); + } + // perform transform if (ipf.needsTransform()) { Imagefloat* trImg = new Imagefloat (fw, fh); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 477782ea6..dc285ce63 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -810,6 +810,10 @@ private: ipf.firstAnalysis (baseImg, params, hist16); + if (params.fattal.enabled) { + ipf.ToneMapFattal02(baseImg); + } + // perform transform (excepted resizing) if (ipf.needsTransform()) { Imagefloat* trImg = nullptr; diff --git a/rtengine/tmo_fattal02.cc b/rtengine/tmo_fattal02.cc new file mode 100644 index 000000000..6e4b45ccb --- /dev/null +++ b/rtengine/tmo_fattal02.cc @@ -0,0 +1,1363 @@ +/* -*- 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" +#define BENCHMARK +#include "StopWatch.h" +#include "sleef.c" +#include "opthelper.h" +namespace rtengine +{ + +/****************************************************************************** + * RT code + ******************************************************************************/ + +extern const Settings *settings; +extern MyMutex *fftwMutex; + +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(); + } +}; + +// upper bound on image dimension used in tmo_fattal02 -- see the comment there +const int RT_dimension_cap = 1920; + +void rescale_bilinear (const Array2Df &src, Array2Df &dst, bool multithread); + + +/****************************************************************************** + * Luminance HDR code (modifications are marked with an RT comment) + ******************************************************************************/ + +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 ; y < height ; y++ ) { + for ( int x = 0 ; x < width ; x++ ) { + float p = A (2 * x, 2 * y); + p += A (2 * x + 1, 2 * y); + p += A (2 * x, 2 * y + 1); + p += A (2 * x + 1, 2 * y + 1); + B (x, y) = p * 0.25f; // p / 4.0f; + } + } +} + +void gaussianBlur (const Array2Df& I, Array2Df& L) +{ + const int width = I.getCols(); + const int height = I.getRows(); + + if (width < 3 || height < 3) { + if (&I != &L) { + for (int i = 0, n = width * height; i < n; ++i) { + L (i) = I (i); + } + } + + return; + } + + Array2Df T (width, height); + + //--- X blur + #pragma omp parallel for shared(I, T) + + for ( int y = 0 ; y < height ; y++ ) { + for ( int x = 1 ; x < width - 1 ; x++ ) { + float t = 2.f * I (x, y); + t += I (x - 1, y); + t += I (x + 1, y); + T (x, y) = t * 0.25f; // t / 4.f; + } + + T (0, y) = ( 3.f * I (0, y) + I (1, y) ) * 0.25f; // / 4.f; + T (width - 1, y) = ( 3.f * I (width - 1, y) + I (width - 2, y) ) * 0.25f; // / 4.f; + } + + //--- Y blur + #pragma omp parallel for + + for ( int x = 0 ; x < width - 7 ; x += 8 ) { + for ( int y = 1 ; y < height - 1 ; y++ ) { + for (int xx = 0; xx < 8; ++xx) { + float t = 2.f * T (x + xx, y); + t += T (x + xx, y - 1); + t += T (x + xx, y + 1); + L (x + xx, y) = t * 0.25f; // t/4.0f; + } + } + + for (int xx = 0; xx < 8; ++xx) { + L (x + xx, 0) = ( 3.f * T (x + xx, 0) + T (x + xx, 1) ) * 0.25f; // / 4.0f; + L (x + xx, height - 1) = ( 3.f * T (x + xx, height - 1) + T (x + xx, height - 2) ) * 0.25f; // / 4.0f; + } + } + + for ( int x = width - (width % 8) ; x < width ; x++ ) { + for ( int y = 1 ; y < height - 1 ; y++ ) { + float t = 2.f * T (x, y); + t += T (x, y - 1); + t += T (x, y + 1); + L (x, y) = t * 0.25f; // t/4.0f; + } + + L (x, 0) = ( 3.f * T (x, 0) + T (x, 1) ) * 0.25f; // / 4.0f; + L (x, height - 1) = ( 3.f * T (x, height - 1) + T (x, height - 2) ) * 0.25f; // / 4.0f; + } +} + +void createGaussianPyramids ( Array2Df* H, Array2Df** pyramids, int nlevels) +{ + int width = H->getCols(); + 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 < size ; i++ ) { + (*pyramids[0]) (i) = (*H) (i); + } + + Array2Df* L = new Array2Df (width, height); + gaussianBlur ( *pyramids[0], *L ); + + for ( int k = 1 ; k < nlevels ; k++ ) { + if (width > 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); + } + } + + if (k < nlevels - 1) { + 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 reduction(+:avgGrad) + + for ( int y = 0 ; y < height ; y++ ) { + int n = (y == 0 ? 0 : y - 1); + int s = (y + 1 == height ? y : y + 1); + + for ( int x = 0 ; x < width ; x++ ) { + float gx, gy; + int w, e; + w = (x == 0 ? 0 : x - 1); + e = (x + 1 == width ? x : x + 1); + + gx = ((*H) (w, y) - (*H) (e, y)); + + gy = ((*H) (x, s) - (*H) (x, n)); + // note this implicitely assumes that H(-1)=H(0) + // for the fft-pde slover this would need adjustment as H(-1)=H(1) + // is assumed, which means gx=0.0, gy=0.0 at the boundaries + // however, the impact is not visible so we ignore this here + + (*G) (x, y) = sqrt (gx * gx + gy * gy) / divider; + avgGrad += (*G) (x, y); + } + } + + return avgGrad / (width * height); +} + +//-------------------------------------------------------------------- + +void upSample (const Array2Df& A, Array2Df& B) +{ + const int width = B.getCols(); + const int height = B.getRows(); + const int awidth = A.getCols(); + const int aheight = A.getRows(); + + //#pragma omp parallel for shared(A, B) + for ( int y = 0 ; y < height ; y++ ) { + for ( int x = 0 ; x < width ; x++ ) { + int ax = static_cast (x * 0.5f); //x / 2.f; + int ay = static_cast (y * 0.5f); //y / 2.f; + ax = (ax < awidth) ? ax : awidth - 1; + ay = (ay < aheight) ? ay : aheight - 1; + + B (x, y) = A (ax, ay); + } + } + +//--- this code below produces 'use of uninitialized value error' +// int width = A->getCols(); +// 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 = pow ((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 < nlevels ; k++ ) { + delete fi[k]; + } + + delete[] fi; +} + +inline +void findMaxMinPercentile (const Array2Df& I, + float minPrct, float& minLum, + float maxPrct, float& maxLum) +{ + assert (minPrct <= maxPrct); + + const int size = I.getRows() * I.getCols(); + const float* data = I.data(); + + // we need to find the (minPrct*size) smallest value and the (maxPrct*size) smallest value in I + // We use a histogram based search for speed and to reduce memory usage + // memory usage of this method is 65536 * sizeof(float) * (t + 1) byte, where t is the number of threads + + // We need one global histogram + LUTu histo (65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE); + histo.clear(); +#ifdef _OPENMP + #pragma omp parallel +#endif + { + // We need one histogram per thread + LUTu histothr (65536, LUT_CLIP_BELOW | LUT_CLIP_ABOVE); + histothr.clear(); + +#ifdef _OPENMP + #pragma omp for nowait +#endif + + for (int i = 0; i < size; ++i) { + // values are in [0;1] range, so we have to multiply with 65535 to get the histogram index + histothr[ (unsigned int) (65535.f * data[i])]++; + } + +#ifdef _OPENMP + #pragma omp critical +#endif + // add per thread histogram to global histogram + histo += histothr; + } + + int k = 0; + int count = 0; + + // find (minPrct*size) smallest value + while (count < minPrct * size) { + count += histo[k++]; + } + + if (k > 0) { // interpolate + int count_ = count - histo[k - 1]; + float c0 = count - minPrct * size; + float c1 = minPrct * size - count_; + minLum = (c1 * k + c0 * (k - 1)) / ((c0 + c1) * 65535.f); + } else { + minLum = k / 65535.f; + } + + // find (maxPrct*size) smallest value + while (count < maxPrct * size) { + count += histo[k++]; + } + + if (k > 0) { // interpolate + int count_ = count - histo[k - 1]; + float c0 = count - maxPrct * size; + float c1 = maxPrct * size - count_; + maxLum = (c1 * k + c0 * (k - 1)) / ((c0 + c1) * 65535.f); + } else { + maxLum = k / 65535.f; + } + +} + +void solve_pde_fft (Array2Df *F, Array2Df *U, Array2Df *buf, bool multithread); + +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; + + // find max value, normalize to range 0..100 and take logarithm + float minLum = Y (0, 0); + float maxLum = Y (0, 0); + + #pragma omp parallel for reduction(max:maxLum) + + for ( int i = 0 ; i < size ; i++ ) { + maxLum = std::max (maxLum, Y (i)); + } + + Array2Df* H = new Array2Df (width, height); + float temp = 100.f / maxLum; + float eps = 1e-4f; + #pragma omp parallel + { +#ifdef __SSE2__ + vfloat epsv = F2V (eps); + vfloat tempv = F2V (temp); +#endif + #pragma omp for schedule(dynamic,16) + + for (size_t i = 0 ; i < height ; ++i) { + size_t j = 0; +#ifdef __SSE2__ + + for (; j < width - 3; j += 4) { + STVFU ((*H)[i][j], xlogf (tempv * LVFU (Y[i][j]) + epsv)); + } + +#endif + + for (; j < width; ++j) { + (*H)[i][j] = xlogf (temp * Y[i][j] + eps); + } + } + } + + /** RT - this is also here to reduce the dependency of the results on the + * input image size, with the primary aim of having a preview in RT that is + * reasonably close to the actual output image. Intuitively, what we do is + * to put a cap on the dimension of the image processed, so that it is close + * in size to the typical preview that you will see on a normal consumer + * monitor. (That's where the 1920 value for RT_dimension_cap comes from.) + * However, we can't simply downscale the input Y array and then upscale it + * on output, because that would cause a big loss of sharpness (confirmed by + * testing). + * So, we use a different method: we downscale the H array, so that we + * compute a downscaled gaussian pyramid and a downscaled FI matrix. Then, + * we upscale the FI matrix later on, before it gets combined with the + * original input luminance array H. This seems to preserve the input + * sharpness and at the same time significantly reduce the dependency of the + * result on the input size. Clearly this is a hack, and keep in mind that I + * do not really know how Fattal works (it comes from LuminanceHDR almost + * verbatim), so this should probably be revised/reviewed by someone who + * knows better... also, we use a quite naive bilinear interpolation + * algorithm (see rescale_bilinear below), which could definitely be + * improved */ + int fullwidth = width; + int fullheight = height; + int dim = std::max (width, height); + Array2Df *fullH = nullptr; + + if (dim > 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 ; k < nlevels ; k++ ) { + gradients[k] = new Array2Df (pyramids[k]->getCols(), pyramids[k]->getRows()); + avgGrad[k] = calculateGradients (pyramids[k], gradients[k], k); + delete pyramids[k]; + } + + delete[] pyramids; + // 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 < nlevels ; i++ ) { + delete gradients[i]; + } + + delete[] gradients; + delete[] avgGrad; + // ph.setValue(16); + // if (ph.canceled()){ + // delete FI; + // delete H; + // return; + // } + + /** - RT - bring back the FI image to the input size if it was downscaled */ + if (fullH) { + Array2Df *FI2 = new Array2Df (fullwidth, fullheight); + rescale_bilinear (*FI, *FI2, multithread); + delete FI; + FI = FI2; + width = fullwidth; + height = fullheight; + delete H; + H = fullH; + } + + /** RT */ + + // attenuate gradients + Array2Df* Gx = new Array2Df (width, height); + Array2Df* Gy = new Array2Df (width, height); + + // the fft solver solves the Poisson pde but with slightly different + // boundary conditions, so we need to adjust the assembly of the right hand + // side accordingly (basically fft solver assumes U(-1) = U(1), whereas zero + // Neumann conditions assume U(-1)=U(0)), see also divergence calculation + // if (fftsolver) + #pragma omp parallel for + + for ( size_t y = 0 ; y < height ; y++ ) { + // sets index+1 based on the boundary assumption H(N+1)=H(N-1) + unsigned int yp1 = (y + 1 >= height ? height - 2 : y + 1); + + for ( size_t x = 0 ; x < width ; x++ ) { + // sets index+1 based on the boundary assumption H(N+1)=H(N-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)); + } + } + + delete H; + + // calculate divergence + #pragma omp parallel for + + for ( size_t y = 0; y < height; ++y ) { + for ( size_t x = 0; x < width; ++x ) { + (*FI) (x, y) = (*Gx) (x, y) + (*Gy) (x, y); + + if ( x > 0 ) { + (*FI) (x, y) -= (*Gx) (x - 1, y); + } + + if ( y > 0 ) { + (*FI) (x, y) -= (*Gy) (x, y - 1); + } + + // if (fftsolver) + { + if (x == 0) { + (*FI) (x, y) += (*Gx) (x, y); + } + + if (y == 0) { + (*FI) (x, y) += (*Gy) (x, y); + } + } + + } + } + + //delete Gx; // RT - reused as temp buffer in solve_pde_fft, deleted later + delete Gy; + + // solve pde and exponentiate (ie recover compressed image) + { + // if (fftsolver) + { + MyMutex::MyLock lock (*fftwMutex); + solve_pde_fft (FI, &L, Gx, multithread); //, ph); + } + delete Gx; + delete FI; + // 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; + // } + #pragma omp parallel + { +#ifdef __SSE2__ + vfloat gammav = F2V (gamma); +#endif + #pragma omp for schedule(dynamic,16) + + for (size_t i = 0 ; i < height ; i++) { + size_t j = 0; +#ifdef __SSE2__ + + for (; j < width - 3; j += 4) { + STVFU (L[i][j], xexpf (gammav * LVFU (L[i][j]))); + } + +#endif + + for (; j < width; j++) { + L[i][j] = xexpf ( gamma * L[i][j]); + } + } + } + } + // 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 < cut_max)); + findMaxMinPercentile (L, cut_min, minLum, cut_max, maxLum); + float dividor = (maxLum - minLum); + + #pragma omp parallel for + + for (size_t i = 0; i < height; ++i) { + for (size_t j = 0; j < width; ++j) { + L[i][j] = std::max ((L[i][j] - minLum) / dividor, 0.f); + // note, we intentionally do not cut off values > 1.0 + } + } +} + + +/** + * + * @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 + #pragma omp parallel for + + for (int y = 1 ; y < height - 1 ; y++ ) + for (int x = 1 ; x < width - 1 ; x++ ) { + (*A) (x, y) *= 0.25f; + } + + for (int x = 1 ; x < width - 1 ; x++ ) { + (*A) (x, 0) *= 0.5f; + (*A) (x, height - 1) *= 0.5f; + } + + for (int y = 1 ; y < height - 1 ; y++ ) { + (*A) (0, y) *= 0.5; + (*A) (width - 1, y) *= 0.5f; + } + + // note, fftw provides its own memory allocation routines which + // ensure that memory is properly 16/32 byte aligned so it can + // use SSE/AVX operations (2/4 double ops in parallel), if our + // data is not properly aligned fftw won't use SSE/AVX + // (I believe new() aligns memory to 16 byte so avoid overhead here) + // + // double* in = (double*) fftwf_malloc(sizeof(double) * width*height); + // fftwf_free(in); + + // 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); +} + + +// 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 + float factor = (1.0f / ((height - 1) * (width - 1))); + #pragma omp parallel for + + for (int y = 0 ; y < height ; y++ ) + for (int x = 0 ; x < width ; x++ ) { + (*T) (x, y) *= factor; + } + + for (int x = 0 ; x < width ; x++ ) { + (*T) (x, 0) *= 0.5f; + (*T) (x, height - 1) *= 0.5f; + } + + for (int y = 0 ; y < height ; y++ ) { + (*T) (0, y) *= 0.5f; + (*T) (width - 1, y) *= 0.5f; + } +} + +// returns the eigenvalues of the 1d laplace operator +std::vector get_lambda (int n) +{ + assert (n > 1); + std::vector v (n); + + for (int i = 0; i < n; i++) { + v[i] = -4.0 * SQR (sin ((double)i / (2 * (n - 1)) * RT_PI)); + } + + return v; +} + +// // makes boundary conditions compatible so that a solution exists +// void make_compatible_boundary(Array2Df *F) +// { +// int width = F->getCols(); +// 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); + assert (buf->getCols() == width && buf->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 = buf; //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); + + #pragma omp parallel for + + for (int y = 0 ; y < height ; y++ ) { + for (int x = 0 ; x < width ; x++ ) { + (*F_tr) (x, y) = (*F_tr) (x, y) / (l1[y] + l2[x]); + } + } + + (*F_tr) (0, 0) = 0.f; // any value ok, only adds a const to the solution + + // transforms U_tr back to the normal space + //DEBUG_STR << "solve_pde_fft: transform U_tr to normal space (fft)" << std::endl; + transform_ev2normal (F_tr, U); +// delete F_tr; // no longer needed so release memory + + // the solution U as calculated will satisfy something like int U = 0 + // since for any constant c, U-c is also a solution and we are mainly + // working in the logspace of (0,1) data we prefer to have + // a solution which has no positive values: U_new(x,y)=U(x,y)-max + // (not really needed but good for numerics as we later take exp(U)) + //DEBUG_STR << "solve_pde_fft: removing constant from solution" << std::endl; + float max = 0.f; + #pragma omp parallel for reduction(max:max) + + for (int i = 0; i < width * height; i++) { + max = std::max (max, (*U) (i)); + } + + #pragma omp parallel for + + for (int i = 0; i < width * height; i++) { + (*U) (i) -= max; + } + + // fft parallel threads cleanup, better handled outside this function? +#ifdef RT_FFTW3F_OMP + + if (multithread) { + fftwf_cleanup_threads(); + } + +#endif + + // ph.setValue(90); + //DEBUG_STR << "solve_pde_fft: done" << std::endl; +} + + +// --------------------------------------------------------------------- +// the functions below are only for test purposes to check the accuracy +// of the pde solvers + + +// // returns the norm of (Laplace U - F) of all interior points +// // useful to compare solvers +// float residual_pde(Array2Df* U, Array2Df* F) +// { +// int width = U->getCols(); +// 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 y = 0; y < dst.getRows(); ++y) { + float ymrs = y * row_scale; + + for (int x = 0; x < dst.getCols(); ++x) { + dst (x, y) = get_bilinear_value (src, x * col_scale, ymrs); + } + } +} + +void rescale_nearest (const Array2Df &src, Array2Df &dst, bool multithread) +{ + const int width = src.getCols(); + const int height = src.getRows(); + const int nw = dst.getCols(); + const int nh = dst.getRows(); + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + + for (int y = 0; y < nh; ++y) { + int sy = y * height / nh; + + for (int x = 0; x < nw; ++x) { + int sx = x * width / nw; + dst (x, y) = src (sx, sy); + } + } +} + + +inline float luminance (float r, float g, float b, TMatrix ws) +{ + return r * ws[1][0] + g * ws[1][1] + b * ws[1][2]; +} + + +inline int round_up_pow2 (int dim) +{ + // from https://graphics.stanford.edu/~seander/bithacks.html + assert (dim > 0); + unsigned int v = dim; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +inline int find_fast_dim (int dim) +{ + // as per the FFTW docs: + // + // FFTW is generally best at handling sizes of the form 2a 3b 5c 7d 11e + // 13f, where e+f is either 0 or 1. + // + // Here, we try to round up to the nearest dim that can be expressed in + // the above form. This is not exhaustive, but should be ok for pictures + // up to 100MPix at least + + int d1 = round_up_pow2 (dim); + std::vector d = { + d1 / 128 * 65, + d1 / 64 * 33, + d1 / 512 * 273, + d1 / 16 * 9, + d1 / 8 * 5, + d1 / 16 * 11, + d1 / 128 * 91, + d1 / 4 * 3, + d1 / 64 * 49, + d1 / 16 * 13, + d1 / 8 * 7, + d1 + }; + + for (size_t i = 0; i < d.size(); ++i) { + if (d[i] >= dim) { + return d[i]; + } + } + + assert (false); + return dim; +} + +} // namespace + + +void ImProcFunctions::ToneMapFattal02 (Imagefloat *rgb) +{ + BENCHFUN + const int detail_level = 3; + + float alpha = 1.f; + + if (params->fattal.threshold < 0) { + alpha += (params->fattal.threshold * 0.9f) / 100.f; + } else if (params->fattal.threshold > 0) { + alpha += params->fattal.threshold / 100.f; + } + + float beta = 1.f - (params->fattal.amount * 0.3f) / 100.f; + + // sanity check + if (alpha <= 0 || beta <= 0) { + return; + } + + int w = rgb->getWidth(); + int h = rgb->getHeight(); + + Array2Df Yr (w, h); + + const float epsilon = 1e-4f; + const float luminance_noise_floor = 65.535f; + const float min_luminance = 1.f; + TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix (params->icm.working); + +#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 (luminance (rgb->r (y, x), rgb->g (y, x), rgb->b (y, x), ws), min_luminance); // clip really black pixels + } + } + + // median filter on the deep shadows, to avoid boosting noise + // because w2 >= w and h2 >= h, we can use the L buffer as temporary buffer for Median_Denoise() + int w2 = find_fast_dim (w) + 1; + int h2 = find_fast_dim (h) + 1; + Array2Df L (w2, h2); + { +#ifdef _OPENMP + int num_threads = multiThread ? omp_get_max_threads() : 1; +#else + int num_threads = 1; +#endif + float r = float (std::max (w, h)) / float (RT_dimension_cap); + Median med; + + if (r >= 3) { + med = Median::TYPE_7X7; + } else if (r >= 2) { + med = Median::TYPE_5X5_STRONG; + } else if (r >= 1) { + med = Median::TYPE_5X5_SOFT; + } else { + med = Median::TYPE_3X3_STRONG; + } + + Median_Denoise (Yr, Yr, luminance_noise_floor, w, h, med, 1, num_threads, L); + } + + float noise = alpha * 0.01f; + + if (settings->verbose) { + std::cout << "ToneMapFattal02: alpha = " << alpha << ", beta = " << beta + << ", detail_level = " << detail_level << std::endl; + } + + rescale_nearest (Yr, L, multiThread); + tmo_fattal02 (w2, h2, L, L, alpha, beta, noise, detail_level, multiThread); + +// tmo_fattal02(w, h, Yr, L, alpha, beta, noise, detail_level, multiThread); + +#ifdef _OPENMP + #pragma omp parallel for if(multiThread) +#endif + + for (int y = 0; y < h; y++) { + int yy = y * h2 / h; + + for (int x = 0; x < w; x++) { + int xx = x * w2 / w; + float Y = Yr (x, y); + float l = std::max (L (xx, yy), epsilon) * (65535.f / Y); + rgb->r (y, x) = std::max (rgb->r (y, x), 0.f) * l; + rgb->g (y, x) = std::max (rgb->g (y, x), 0.f) * l; + rgb->b (y, x) = std::max (rgb->b (y, x), 0.f) * l; + + assert (std::isfinite (rgb->r (y, x))); + assert (std::isfinite (rgb->g (y, x))); + assert (std::isfinite (rgb->b (y, x))); + } + } +} + + +} // namespace rtengine diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index e8bbf18b1..36c7a4034 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -147,6 +147,7 @@ set(NONCLISOURCEFILES xtransprocess.cc xtransrawexposure.cc zoompanel.cc + fattaltonemap.cc ) include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 2ee2e6053..07cf47d18 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -122,6 +122,13 @@ enum { ADDSET_SHARP_EDGETOL, ADDSET_SHARP_HALOCTRL, ADDSET_RESIZE_SCALE, + ADDSET_EPD_STRENGTH, + ADDSET_EPD_GAMMA, + ADDSET_EPD_EDGESTOPPING, + ADDSET_EPD_SCALE, + ADDSET_EPD_REWEIGHTINGITERATES, + ADDSET_FATTAL_ALPHA, + ADDSET_FATTAL_BETA, ADDSET_PARAM_NUM // THIS IS USED AS A DELIMITER!! }; diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index e7672e4c7..a854db612 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -151,6 +151,8 @@ void BatchToolPanelCoordinator::initSession () sharpenEdge->setAdjusterBehavior (false, false); sharpenMicro->setAdjusterBehavior (false, false); icm->setAdjusterBehavior (false, false); + epd->setAdjusterBehavior (false, false, false, false, false); + fattal->setAdjusterBehavior (false, false); chmixer->setAdjusterBehavior (false); blackwhite->setAdjusterBehavior (false, false); @@ -189,6 +191,8 @@ void BatchToolPanelCoordinator::initSession () cacorrection->setAdjusterBehavior (options.baBehav[ADDSET_CA]); sharpening->setAdjusterBehavior (options.baBehav[ADDSET_SHARP_RADIUS], options.baBehav[ADDSET_SHARP_AMOUNT], options.baBehav[ADDSET_SHARP_DAMPING], options.baBehav[ADDSET_SHARP_ITER], options.baBehav[ADDSET_SHARP_EDGETOL], options.baBehav[ADDSET_SHARP_HALOCTRL]); prsharpening->setAdjusterBehavior (options.baBehav[ADDSET_SHARP_RADIUS], options.baBehav[ADDSET_SHARP_AMOUNT], options.baBehav[ADDSET_SHARP_DAMPING], options.baBehav[ADDSET_SHARP_ITER], options.baBehav[ADDSET_SHARP_EDGETOL], options.baBehav[ADDSET_SHARP_HALOCTRL]); + epd->setAdjusterBehavior (options.baBehav[ADDSET_EPD_STRENGTH], options.baBehav[ADDSET_EPD_GAMMA], options.baBehav[ADDSET_EPD_EDGESTOPPING], options.baBehav[ADDSET_EPD_SCALE], options.baBehav[ADDSET_EPD_REWEIGHTINGITERATES]); + fattal->setAdjusterBehavior (options.baBehav[ADDSET_FATTAL_ALPHA], options.baBehav[ADDSET_FATTAL_BETA]); sharpenEdge->setAdjusterBehavior (options.baBehav[ADDSET_SHARPENEDGE_AMOUNT], options.baBehav[ADDSET_SHARPENEDGE_PASS]); sharpenMicro->setAdjusterBehavior (options.baBehav[ADDSET_SHARPENMICRO_AMOUNT], options.baBehav[ADDSET_SHARPENMICRO_UNIFORMITY]); diff --git a/rtgui/epd.cc b/rtgui/epd.cc index d7848aee0..602585dfb 100644 --- a/rtgui/epd.cc +++ b/rtgui/epd.cc @@ -182,3 +182,11 @@ void EdgePreservingDecompositionUI::setBatchMode(bool batchMode) reweightingIterates->showEditedCB(); } +void EdgePreservingDecompositionUI::setAdjusterBehavior (bool stAdd, bool gAdd, bool esAdd, bool scAdd, bool rAdd) +{ + strength->setAddMode(stAdd); + gamma->setAddMode(gAdd); + edgeStopping->setAddMode(esAdd); + scale->setAddMode(scAdd); + reweightingIterates->setAddMode(rAdd); +} diff --git a/rtgui/epd.h b/rtgui/epd.h index c9fc5d0af..2e18cc0c9 100644 --- a/rtgui/epd.h +++ b/rtgui/epd.h @@ -43,6 +43,7 @@ public: void adjusterChanged (Adjuster* a, double newval); void enabledChanged (); + void setAdjusterBehavior (bool stAdd, bool gAdd, bool esAdd, bool scAdd, bool rAdd); }; #endif diff --git a/rtgui/fattaltonemap.cc b/rtgui/fattaltonemap.cc new file mode 100644 index 000000000..d14b76004 --- /dev/null +++ b/rtgui/fattaltonemap.cc @@ -0,0 +1,126 @@ +/** -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2017 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 . + */ +#include "fattaltonemap.h" +#include +#include + +using namespace rtengine; +using namespace rtengine::procparams; + +FattalToneMapping::FattalToneMapping(): FoldableToolPanel(this, "fattal", M("TP_TM_FATTAL_LABEL"), true, true) +{ + +// setEnabledTooltipMarkup(M("TP_EPD_TOOLTIP")); + + amount = Gtk::manage(new Adjuster (M("TP_TM_FATTAL_AMOUNT"), 0., 100., 1., 0.0)); + threshold = Gtk::manage(new Adjuster (M("TP_TM_FATTAL_THRESHOLD"), -100., 100., 1., 0.0)); + + amount->setAdjusterListener(this); + threshold->setAdjusterListener(this); + + amount->show(); + threshold->show(); + + pack_start(*amount); + pack_start(*threshold); +} + +void FattalToneMapping::read(const ProcParams *pp, const ParamsEdited *pedited) +{ + disableListener(); + + if(pedited) { + threshold->setEditedState(pedited->fattal.threshold ? Edited : UnEdited); + amount->setEditedState(pedited->fattal.amount ? Edited : UnEdited); + set_inconsistent(multiImage && !pedited->fattal.enabled); + } + + setEnabled(pp->fattal.enabled); + threshold->setValue(pp->fattal.threshold); + amount->setValue(pp->fattal.amount); + + enableListener(); +} + +void FattalToneMapping::write(ProcParams *pp, ParamsEdited *pedited) +{ + pp->fattal.threshold = threshold->getValue(); + pp->fattal.amount = amount->getValue(); + pp->fattal.enabled = getEnabled(); + + if(pedited) { + pedited->fattal.threshold = threshold->getEditedState(); + pedited->fattal.amount = amount->getEditedState(); + pedited->fattal.enabled = !get_inconsistent(); + } +} + +void FattalToneMapping::setDefaults(const ProcParams *defParams, const ParamsEdited *pedited) +{ + threshold->setDefault(defParams->fattal.threshold); + amount->setDefault(defParams->fattal.amount); + + if(pedited) { + threshold->setDefaultEditedState(pedited->fattal.threshold ? Edited : UnEdited); + amount->setDefaultEditedState(pedited->fattal.amount ? Edited : UnEdited); + } else { + threshold->setDefaultEditedState(Irrelevant); + amount->setDefaultEditedState(Irrelevant); + } +} + +void FattalToneMapping::adjusterChanged(Adjuster* a, double newval) +{ + if(listener && getEnabled()) { + if(a == threshold) { + listener->panelChanged(EvTMFattalThreshold, a->getTextValue()); + } else if(a == amount) { + listener->panelChanged(EvTMFattalAmount, a->getTextValue()); + } + } +} + +void FattalToneMapping::enabledChanged () +{ + if (listener) { + if (get_inconsistent()) { + listener->panelChanged (EvTMFattalEnabled, M("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged (EvTMFattalEnabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged (EvTMFattalEnabled, M("GENERAL_DISABLED")); + } + } +} + +void FattalToneMapping::setBatchMode(bool batchMode) +{ + ToolPanel::setBatchMode(batchMode); + + threshold->showEditedCB(); + amount->showEditedCB(); +} + +void FattalToneMapping::setAdjusterBehavior (bool alphaAdd, bool betaAdd) +{ + threshold->setAddMode(alphaAdd); + amount->setAddMode(betaAdd); +} + diff --git a/rtgui/fattaltonemap.h b/rtgui/fattaltonemap.h new file mode 100644 index 000000000..2398970ce --- /dev/null +++ b/rtgui/fattaltonemap.h @@ -0,0 +1,46 @@ +/** -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2017 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 . + */ +#pragma once + +#include +#include "adjuster.h" +#include "toolpanel.h" + +class FattalToneMapping: public ToolParamBlock, public AdjusterListener, public FoldableToolPanel +{ +protected: + Adjuster *threshold; + Adjuster *amount; + +public: + + FattalToneMapping(); + + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr); + void setBatchMode (bool batchMode); + + void adjusterChanged (Adjuster* a, double newval); + void enabledChanged (); + void setAdjusterBehavior (bool alphaAdd, bool betaAdd); + +}; + diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 0827a0d7f..dc1a65b64 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -264,6 +264,9 @@ void ParamsEdited::set (bool v) epd.edgeStopping = v; epd.scale = v; epd.reweightingIterates = v; + fattal.enabled = v; + fattal.threshold = v; + fattal.amount = v; sh.enabled = v; sh.hq = v; sh.highlights = v; @@ -804,6 +807,10 @@ void ParamsEdited::initFrom (const std::vector epd.scale = epd.scale && p.epd.scale == other.epd.scale; epd.reweightingIterates = epd.reweightingIterates && p.epd.reweightingIterates == other.epd.reweightingIterates; + fattal.enabled = fattal.enabled && p.fattal.enabled == other.fattal.enabled; + fattal.threshold = fattal.threshold && p.fattal.threshold == other.fattal.threshold; + fattal.amount = fattal.amount && p.fattal.amount == other.fattal.amount; + sh.enabled = sh.enabled && p.sh.enabled == other.sh.enabled; sh.hq = sh.hq && p.sh.hq == other.sh.hq; sh.highlights = sh.highlights && p.sh.highlights == other.sh.highlights; @@ -1972,6 +1979,16 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten toEdit.epd.reweightingIterates = mods.epd.reweightingIterates; } + if (fattal.enabled) { + toEdit.fattal.enabled = mods.fattal.enabled; + } + if (fattal.threshold) { + toEdit.fattal.threshold = mods.fattal.threshold; + } + if (fattal.amount) { + toEdit.fattal.amount = mods.fattal.amount; + } + if (sh.enabled) { toEdit.sh.enabled = mods.sh.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 46a68d3f5..4d205b1f5 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -365,6 +365,14 @@ public: }; +class FattalToneMappingParamsEdited { +public: + bool enabled; + bool threshold; + bool amount; +}; + + class SHParamsEdited { @@ -800,6 +808,7 @@ public: DefringeParamsEdited defringe; DirPyrDenoiseParamsEdited dirpyrDenoise; EPDParamsEdited epd; + FattalToneMappingParamsEdited fattal; ImpulseDenoiseParamsEdited impulseDenoise; SHParamsEdited sh; CropParamsEdited crop; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index ce26078fc..7d91e0172 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -51,6 +51,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren exposure = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_EXPOSURE"))); sh = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_SHADOWSHIGHLIGHTS"))); epd = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_EPD"))); + fattal = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_TM_FATTAL"))); retinex = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_RETINEX"))); pcvignette = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_PCVIGNETTE"))); gradient = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_GRADIENT"))); @@ -143,6 +144,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[0]->pack_start (*exposure, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*sh, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*epd, Gtk::PACK_SHRINK, 2); + vboxes[0]->pack_start (*fattal, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*retinex, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*pcvignette, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*gradient, Gtk::PACK_SHRINK, 2); @@ -298,6 +300,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren exposureConn = exposure->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); shConn = sh->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); epdConn = epd->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); + fattalConn = fattal->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); retinexConn = retinex->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); pcvignetteConn = pcvignette->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); gradientConn = gradient->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); @@ -517,6 +520,7 @@ void PartialPasteDlg::basicToggled () exposure->set_active (basic->get_active ()); sh->set_active (basic->get_active ()); epd->set_active (basic->get_active ()); + fattal->set_active (basic->get_active ()); pcvignette->set_active (basic->get_active ()); gradient->set_active (basic->get_active ()); retinex->set_active (basic->get_active ()); @@ -711,6 +715,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.epd = falsePE.epd; } + if (!fattal->get_active ()) { + filterPE.fattal = falsePE.fattal; + } + if (!retinex->get_active ()) { filterPE.retinex = falsePE.retinex; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 8fa6dbd23..baef6b9aa 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -46,6 +46,7 @@ public: Gtk::CheckButton* exposure; Gtk::CheckButton* sh; Gtk::CheckButton* epd; + Gtk::CheckButton* fattal; Gtk::CheckButton* retinex; Gtk::CheckButton* pcvignette; Gtk::CheckButton* gradient; @@ -124,7 +125,7 @@ public: sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaConn, rawConn, wavConn; sigc::connection wbConn, exposureConn, shConn, pcvignetteConn, gradientConn, labcurveConn, colorappearanceConn; - sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, dirpyreqConn, waveletConn, retinexConn; + sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn; sigc::connection vibranceConn, chmixerConn, hsveqConn, rgbcurvesConn, chmixerbwConn, colortoningConn, filmSimulationConn; sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, perspectiveConn, commonTransConn; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index f8ad0ef5c..3f414321b 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -187,6 +187,19 @@ Gtk::Widget* Preferences::getBatchProcPanel () appendBehavList (mi, M ("TP_EXPOSURE_CONTRAST"), ADDSET_TC_CONTRAST, false); appendBehavList (mi, M ("TP_EXPOSURE_SATURATION"), ADDSET_TC_SATURATION, false); + mi = behModel->append (); + mi->set_value (behavColumns.label, M ("TP_EPD_LABEL")); + appendBehavList (mi, M ("TP_EPD_STRENGTH"), ADDSET_EPD_STRENGTH, false); + appendBehavList (mi, M ("TP_EPD_GAMMA"), ADDSET_EPD_GAMMA, false); + appendBehavList (mi, M ("TP_EPD_EDGESTOPPING"), ADDSET_EPD_EDGESTOPPING, false); + appendBehavList (mi, M ("TP_EPD_SCALE"), ADDSET_EPD_SCALE, false); + appendBehavList (mi, M ("TP_EPD_REWEIGHTINGITERATES"), ADDSET_EPD_REWEIGHTINGITERATES, false); + + mi = behModel->append (); + mi->set_value (behavColumns.label, M ("TP_TM_FATTAL_LABEL")); + appendBehavList (mi, M ("TP_TM_FATTAL_ALPHA"), ADDSET_FATTAL_ALPHA, false); + appendBehavList (mi, M ("TP_TM_FATTAL_BETA"), ADDSET_FATTAL_BETA, false); + mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_RETINEX_LABEL")); appendBehavList (mi, M ("TP_RETINEX_STRENGTH"), ADDSET_RETI_STR, false); diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index ed84ae8b6..9153c6fb4 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -90,6 +90,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), hasChan rawexposure = Gtk::manage (new RAWExposure ()); bayerrawexposure = Gtk::manage (new BayerRAWExposure ()); xtransrawexposure = Gtk::manage (new XTransRAWExposure ()); + fattal = Gtk::manage (new FattalToneMapping ()); // So Demosaic, Line noise filter, Green Equilibration, Ca-Correction (garder le nom de section identique!) and Black-Level will be moved in a "Bayer sensor" tool, // and a separate Demosaic and Black Level tool will be created in an "X-Trans sensor" tool @@ -114,6 +115,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), hasChan addPanel (colorPanel, rgbcurves); addPanel (colorPanel, colortoning); addPanel (exposurePanel, epd); + addPanel (exposurePanel, fattal); addPanel (exposurePanel, retinex); addPanel (exposurePanel, pcvignette); addPanel (exposurePanel, gradient); diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 155679687..3da061b99 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -78,6 +78,7 @@ #include "colortoning.h" #include "filmsimulation.h" #include "prsharpening.h" +#include "fattaltonemap.h" #include "guiutils.h" class ImageEditorCoordinator; @@ -145,6 +146,7 @@ protected: RAWExposure* rawexposure; BayerRAWExposure* bayerrawexposure; XTransRAWExposure* xtransrawexposure; + FattalToneMapping *fattal; std::vector paramcListeners;