diff --git a/rtdata/languages/default b/rtdata/languages/default index a3e99cfc2..6dd32ef7b 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1520,6 +1520,11 @@ HISTORY_MSG_SPOT_ENTRY;Spot removal - Point modif. HISTORY_MSG_TEMPOUT;CAM02 automatic temperature HISTORY_MSG_THRESWAV;Balance threshold HISTORY_MSG_TM_FATTAL_ANCHOR;DRC - Anchor +HISTORY_MSG_TONE_EQUALIZER_BANDS;Tone equalizer - Bands +HISTORY_MSG_TONE_EQUALIZER_ENABLED;Tone equalizer +HISTORY_MSG_TONE_EQUALIZER_PIVOT;Tone equalizer - Pivot +HISTORY_MSG_TONE_EQUALIZER_REGULARIZATION;Tone equalizer - Regularization +HISTORY_MSG_TONE_EQUALIZER_SHOW_COLOR_MAP;Tone equalizer - Tonal map HISTORY_MSG_TRANS_METHOD;Geometry - Method HISTORY_MSG_WAVBALCHROM;Equalizer chrominance HISTORY_MSG_WAVBALLUM;Equalizer luminance @@ -1827,6 +1832,7 @@ PARTIALPASTE_SHARPENMICRO;Microcontrast PARTIALPASTE_SOFTLIGHT;Soft light PARTIALPASTE_SPOT;Spot removal PARTIALPASTE_TM_FATTAL;Dynamic range compression +PARTIALPASTE_TONE_EQUALIZER;Tone equalizer PARTIALPASTE_VIBRANCE;Vibrance PARTIALPASTE_VIGNETTING;Vignetting correction PARTIALPASTE_WHITEBALANCE;White balance @@ -3837,6 +3843,15 @@ TP_TM_FATTAL_AMOUNT;Amount TP_TM_FATTAL_ANCHOR;Anchor TP_TM_FATTAL_LABEL;Dynamic Range Compression TP_TM_FATTAL_THRESHOLD;Detail +TP_TONE_EQUALIZER_BAND_0;Blacks +TP_TONE_EQUALIZER_BAND_1;Shadows +TP_TONE_EQUALIZER_BAND_2;Midtones +TP_TONE_EQUALIZER_BAND_3;Highlights +TP_TONE_EQUALIZER_BAND_4;Whites +TP_TONE_EQUALIZER_DETAIL;Regularization +TP_TONE_EQUALIZER_LABEL;Tone Equalizer +TP_TONE_EQUALIZER_PIVOT;Pivot (Ev) +TP_TONE_EQUALIZER_SHOW_COLOR_MAP;Show tonal map 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 1f3cf352b..3cc8df625 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -133,6 +133,7 @@ set(RTENGINESOURCEFILES ipsharpen.cc ipsharpenedges.cc ipsoftlight.cc + iptoneequalizer.cc iptransform.cc ipvibrance.cc ipwavelet.cc diff --git a/rtengine/imagefloat.cc b/rtengine/imagefloat.cc index 7bc75fe6d..e4cb28e6b 100644 --- a/rtengine/imagefloat.cc +++ b/rtengine/imagefloat.cc @@ -341,6 +341,38 @@ void Imagefloat::getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* imag #endif } +// From ART. +void Imagefloat::multiply(float factor, bool multithread) +{ + const int W = width; + const int H = height; +#ifdef __SSE2__ + vfloat vfactor = F2V(factor); +#endif + +#ifdef _OPENMP +# pragma omp parallel for firstprivate(W, H) schedule(dynamic, 5) if (multithread) +#endif + for (int y = 0; y < H; y++) { + int x = 0; +#ifdef __SSE2__ + for (; x < W-3; x += 4) { + vfloat rv = LVF(r(y, x)); + vfloat gv = LVF(g(y, x)); + vfloat bv = LVF(b(y, x)); + STVF(r(y, x), rv * vfactor); + STVF(g(y, x), gv * vfactor); + STVF(b(y, x), bv * vfactor); + } +#endif + for (; x < W; ++x) { + r(y, x) *= factor; + g(y, x) *= factor; + b(y, x) *= factor; + } + } +} + void Imagefloat::normalizeFloat(float srcMinVal, float srcMaxVal) { @@ -362,43 +394,15 @@ void Imagefloat::normalizeFloat(float srcMinVal, float srcMaxVal) } // convert values's range to [0;1] ; this method assumes that the input values's range is [0;65535] -void Imagefloat::normalizeFloatTo1() +void Imagefloat::normalizeFloatTo1(bool multithread) { - - const int w = width; - const int h = height; - -#ifdef _OPENMP - #pragma omp parallel for schedule(dynamic, 5) -#endif - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - r(y, x) /= 65535.f; - g(y, x) /= 65535.f; - b(y, x) /= 65535.f; - } - } + multiply(1.f/65535.f, multithread); } // convert values's range to [0;65535 ; this method assumes that the input values's range is [0;1] -void Imagefloat::normalizeFloatTo65535() +void Imagefloat::normalizeFloatTo65535(bool multithread) { - - const int w = width; - const int h = height; - -#ifdef _OPENMP - #pragma omp parallel for schedule(dynamic, 5) -#endif - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - r(y, x) *= 65535.f; - g(y, x) *= 65535.f; - b(y, x) *= 65535.f; - } - } + multiply(65535.f, multithread); } // Parallelized transformation; create transform with cmsFLAGS_NOCACHE! diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index fc3ba318d..3bee52d9a 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -207,9 +207,10 @@ public: return (uint32_t) ((lsign << 31) | (exponent << 23) | mantissa); } + void multiply(float factor, bool multithread); void normalizeFloat(float srcMinVal, float srcMaxVal) override; - void normalizeFloatTo1(); - void normalizeFloatTo65535(); + void normalizeFloatTo1(bool multithread=true); + void normalizeFloatTo65535(bool multithread=true); void ExecCMSTransform(cmsHTRANSFORM hTransform); void ExecCMSTransform(cmsHTRANSFORM hTransform, const LabImage &labImage, int cx, int cy); }; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 33cce1842..cb9f6500f 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -2734,6 +2734,7 @@ void ImProcCoordinator::process() || params->epd != nextParams->epd || params->fattal != nextParams->fattal || params->sh != nextParams->sh + || params->toneEqualizer != nextParams->toneEqualizer || params->crop != nextParams->crop || params->coarse != nextParams->coarse || params->commonTrans != nextParams->commonTrans diff --git a/rtengine/improcfun.cc b/rtengine/improcfun.cc index 15ebcc2e6..e50e28a01 100644 --- a/rtengine/improcfun.cc +++ b/rtengine/improcfun.cc @@ -2258,6 +2258,12 @@ void ImProcFunctions::rgbProc (Imagefloat* working, LabImage* lab, PipetteBuffer // For tonecurve histogram const float lumimulf[3] = {static_cast(lumimul[0]), static_cast(lumimul[1]), static_cast(lumimul[2])}; + std::unique_ptr workimg_copy; + if (params->toneEqualizer.enabled) { + working = working->copy(); + workimg_copy.reset(working); + toneEqualizer(working); + } #define TS 112 diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 1be1d0371..70aed428c 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -97,6 +97,7 @@ struct LocalContrastParams; struct LocallabParams; struct SharpeningParams; struct SoftLightParams; +struct ToneEqualizerParams; struct VibranceParams; struct VignettingParams; struct WaveletParams; @@ -491,7 +492,8 @@ enum class BlurType { void colorToningLabGrid(LabImage *lab, int xstart, int xend, int ystart, int yend, bool MultiThread); //void shadowsHighlights(LabImage *lab); void shadowsHighlights(LabImage *lab, bool ena, int labmode, int hightli, int shado, int rad, int scal, int hltonal, int shtonal); - + bool toneEqualizer(Imagefloat *rgb); + static void toneEqualizer(array2D &R, array2D &G, array2D &B, const procparams::ToneEqualizerParams & params, const Glib::ustring &workingProfile, double scale, bool multithread, bool show_color_map); void softLight(LabImage *lab, const procparams::SoftLightParams &softLightParams); void labColorCorrectionRegions(LabImage *lab); diff --git a/rtengine/iplocallab.cc b/rtengine/iplocallab.cc index ac87c75d7..7a427b0eb 100644 --- a/rtengine/iplocallab.cc +++ b/rtengine/iplocallab.cc @@ -2258,230 +2258,11 @@ void ImProcFunctions::getAutoLogloc(int sp, ImageSource *imgsrc, float *sourceg, } void tone_eq(array2D &R, array2D &G, array2D &B, const struct local_params & lp, const Glib::ustring &workingProfile, double scale, bool multithread) -// adapted from the tone equalizer of darktable -/* - Copyright 2019 Alberto Griggio - Small adaptation to Local Adjustment 10 2019 Jacques Desmis - This file is part of darktable, - copyright (c) 2018 Aurelien Pierre. - - darktable 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. - - darktable 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 darktable. If not, see . -*/ - { - // BENCHFUN - - const int W = R.getWidth(); - const int H = R.getHeight(); - array2D Y(W, H); - - const auto log2 = - [](float x) -> float { - static const float l2 = xlogf(2); - return xlogf(x) / l2; - }; - - const auto exp2 = - [](float x) -> float { - return pow_F(2.f, x); - }; - // Build the luma channels: band-pass filters with gaussian windows of - // std 2 EV, spaced by 2 EV - const float centers[12] = { - -18.0f, -16.0f, -14.0f, -12.0f, -10.0f, -8.0f, -6.0f, - -4.0f, -2.0f, 0.0f, 2.0f, 4.0f - }; - - const auto conv = [&](int v, float lo, float hi) -> float { - const float f = v < 0 ? lo : hi; - return exp2(float(v) / 100.f * f); - }; - const float factors[12] = { - conv(lp.mullocsh[0], 2.f, 3.f), // -18 EV - conv(lp.mullocsh[0], 2.f, 3.f), // -16 EV - conv(lp.mullocsh[0], 2.f, 3.f), // -14 EV - conv(lp.mullocsh[0], 2.f, 3.f), // -12 EV - conv(lp.mullocsh[0], 2.f, 3.f), // -10 EV - conv(lp.mullocsh[0], 2.f, 3.f), // -8 EV - conv(lp.mullocsh[1], 2.f, 3.f), // -6 EV - conv(lp.mullocsh[2], 2.5f, 2.5f), // -4 EV - conv(lp.mullocsh[3], 3.f, 2.f), // -2 EV - conv(lp.mullocsh[4], 3.f, 2.f), // 0 EV - conv(lp.mullocsh[4], 3.f, 2.f), // 2 EV - conv(lp.mullocsh[4], 3.f, 2.f) // 4 EV - }; - - TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(workingProfile); - -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - for (int y = 0; y < H; ++y) { - for (int x = 0; x < W; ++x) { - Y[y][x] = Color::rgbLuminance(R[y][x], G[y][x], B[y][x], ws); - } - } - - int detail = LIM(lp.detailsh + 5, 0, 5); - int radius = detail / scale + 0.5; - float epsilon2 = 0.01f + 0.002f * rtengine::max(detail - 3, 0); - - if (radius > 0) { - rtengine::guidedFilterLog(10.f, Y, radius, epsilon2, multithread); - } - - if (lp.detailsh > 0) { - array2D Y2(W, H); - constexpr float base_epsilon = 0.02f; - constexpr float base_posterization = 5.f; - -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - for (int y = 0; y < H; ++y) { - for (int x = 0; x < W; ++x) { - float l = LIM(log2(rtengine::max(Y[y][x], 1e-9f)), centers[0], centers[11]); - float ll = round(l * base_posterization) / base_posterization; - Y2[y][x] = Y[y][x]; - Y[y][x] = exp2(ll); - } - } - - radius = 350.0 / scale; - epsilon2 = base_epsilon / float(6 - rtengine::min(lp.detailsh, 5)); - rtengine::guidedFilter(Y2, Y, Y, radius, epsilon2, multithread); - } - - const auto gauss = - [](float b, float x) -> float { - return xexpf((-SQR(x - b) / 4.0f)); - }; - - // For every pixel luminance, the sum of the gaussian masks - float w_sum = 0.f; - - for (int i = 0; i < 12; ++i) { - w_sum += gauss(centers[i], 0.f); - } - - const auto process_pixel = - [&](float y) -> float { - // convert to log space - const float luma = rtengine::max(log2(rtengine::max(y, 0.f)), -18.0f); - - // build the correction as the sum of the contribution of each - // luminance channel to current pixel - float correction = 0.0f; - - for (int c = 0; c < 12; ++c) - { - correction += gauss(centers[c], luma) * factors[c]; - } - - correction /= w_sum; - - return correction; - }; - - LUTf lut(65536); - - for (int i = 0; i < 65536; ++i) { - float y = float(i) / 65535.f; - float c = process_pixel(y); - lut[i] = c; - } - - -#ifdef __SSE2__ - vfloat vfactors[12]; - vfloat vcenters[12]; - - for (int i = 0; i < 12; ++i) { - vfactors[i] = F2V(factors[i]); - vcenters[i] = F2V(centers[i]); - } - - const auto vgauss = - [](vfloat b, vfloat x) -> vfloat { - static const vfloat fourv = F2V(4.f); - return xexpf((-SQR(x - b) / fourv)); - }; - - vfloat zerov = F2V(0.f); - vfloat vw_sum = F2V(w_sum); - - const vfloat noisev = F2V(-18.f); - const vfloat xlog2v = F2V(xlogf(2.f)); - - const auto vprocess_pixel = - [&](vfloat y) -> vfloat { - const vfloat luma = vmaxf(xlogf(vmaxf(y, zerov)) / xlog2v, noisev); - - vfloat correction = zerov; - - for (int c = 0; c < 12; ++c) - { - correction += vgauss(vcenters[c], luma) * vfactors[c]; - } - - correction /= vw_sum; - - return correction; - }; - - - vfloat v1 = F2V(1.f); - vfloat v65535 = F2V(65535.f); -#endif // __SSE2__ - - -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - for (int y = 0; y < H; ++y) { - int x = 0; - - -#ifdef __SSE2__ - - for (; x < W - 3; x += 4) { - vfloat cY = LVFU(Y[y][x]); - vmask m = vmaskf_gt(cY, v1); - vfloat corr; - - if (_mm_movemask_ps((vfloat)m)) { - corr = vprocess_pixel(cY); - } else { - corr = lut[cY * v65535]; - } - - STVF(R[y][x], LVF(R[y][x]) * corr); - STVF(G[y][x], LVF(G[y][x]) * corr); - STVF(B[y][x], LVF(B[y][x]) * corr); - } - -#endif // __SSE2__ - - for (; x < W; ++x) { - float cY = Y[y][x]; - float corr = cY > 1.f ? process_pixel(cY) : lut[cY * 65535.f]; - R[y][x] *= corr; - G[y][x] *= corr; - B[y][x] *= corr; - } - } - + ToneEqualizerParams params; + params.regularization = lp.detailsh; + std::copy(lp.mullocsh, lp.mullocsh + params.bands.size(), params.bands.begin()); + ImProcFunctions::toneEqualizer(R, G, B, params, workingProfile, scale, multithread, false); } void ImProcFunctions::loccont(int bfw, int bfh, LabImage* tmp1, float rad, float stren, int sk) { diff --git a/rtengine/iptoneequalizer.cc b/rtengine/iptoneequalizer.cc new file mode 100644 index 000000000..c177b8f98 --- /dev/null +++ b/rtengine/iptoneequalizer.cc @@ -0,0 +1,362 @@ +#include "color.h" +#include "guidedfilter.h" +#include "iccstore.h" +#include "imagefloat.h" +#include "improcfun.h" +#include "sleef.h" +#include "StopWatch.h" + + +namespace +{ + +const std::vector> colormap = { + {0.5f, 0.f, 0.5f}, + {0.5f, 0.f, 0.5f}, + {0.5f, 0.f, 0.5f}, + {0.5f, 0.f, 0.5f}, + {0.5f, 0.f, 0.5f}, + {0.5f, 0.f, 0.5f}, // blacks + {0.f, 0.f, 1.f}, // shadows + {0.5f, 0.5f, 0.5f}, // midtones + {1.f, 1.f, 0.f}, // highlights + {1.f, 0.f, 0.f}, // whites + {1.f, 0.f, 0.f}, + {1.f, 0.f, 0.f} +}; + +} + + +namespace rtengine +{ + +void ImProcFunctions::toneEqualizer( + array2D &R, array2D &G, array2D &B, + const struct ToneEqualizerParams ¶ms, + const Glib::ustring &workingProfile, + double scale, + bool multithread, + bool show_color_map) +// adapted from the tone equalizer of darktable +/* + Copyright 2019 Alberto Griggio + Small adaptation to Local Adjustment 10 2019 Jacques Desmis + This file is part of darktable, + copyright (c) 2018 Aurelien Pierre. + + darktable 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. + + darktable 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 darktable. If not, see . +*/ + +{ + // BENCHFUN + + const int W = R.getWidth(); + const int H = R.getHeight(); + array2D Y(W, H); + + const auto log2 = + [](float x) -> float { + static const float l2 = xlogf(2); + return xlogf(x) / l2; + }; + + const auto exp2 = + [](float x) -> float { + return pow_F(2.f, x); + }; + // Build the luma channels: band-pass filters with gaussian windows of + // std 2 EV, spaced by 2 EV + const float centers[12] = { + -18.0f, -16.0f, -14.0f, -12.0f, -10.0f, -8.0f, -6.0f, + -4.0f, -2.0f, 0.0f, 2.0f, 4.0f + }; + + const auto conv = [&](int v, float lo, float hi) -> float { + const float f = v < 0 ? lo : hi; + return exp2(float(v) / 100.f * f); + }; + const float factors[12] = { + conv(params.bands[0], 2.f, 3.f), // -18 EV + conv(params.bands[0], 2.f, 3.f), // -16 EV + conv(params.bands[0], 2.f, 3.f), // -14 EV + conv(params.bands[0], 2.f, 3.f), // -12 EV + conv(params.bands[0], 2.f, 3.f), // -10 EV + conv(params.bands[0], 2.f, 3.f), // -8 EV + conv(params.bands[1], 2.f, 3.f), // -6 EV + conv(params.bands[2], 2.5f, 2.5f), // -4 EV + conv(params.bands[3], 3.f, 2.f), // -2 EV + conv(params.bands[4], 3.f, 2.f), // 0 EV + conv(params.bands[4], 3.f, 2.f), // 2 EV + conv(params.bands[4], 3.f, 2.f) // 4 EV + }; + + TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(workingProfile); + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + Y[y][x] = Color::rgbLuminance(R[y][x], G[y][x], B[y][x], ws); + } + } + + int detail = LIM(params.regularization + 5, 0, 5); + int radius = detail / scale + 0.5; + float epsilon2 = 0.01f + 0.002f * rtengine::max(detail - 3, 0); + + if (radius > 0) { + rtengine::guidedFilterLog(10.f, Y, radius, epsilon2, multithread); + } + + if (params.regularization > 0) { + array2D Y2(W, H); + constexpr float base_epsilon = 0.02f; + constexpr float base_posterization = 5.f; + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float l = LIM(log2(rtengine::max(Y[y][x], 1e-9f)), centers[0], centers[11]); + float ll = round(l * base_posterization) / base_posterization; + Y2[y][x] = Y[y][x]; + Y[y][x] = exp2(ll); + } + } + + radius = 350.0 / scale; + epsilon2 = base_epsilon / float(6 - rtengine::min(params.regularization, 5)); + rtengine::guidedFilter(Y2, Y, Y, radius, epsilon2, multithread); + } + + const auto gauss = + [](float b, float x) -> float { + return xexpf((-SQR(x - b) / 4.0f)); + }; + + // For every pixel luminance, the sum of the gaussian masks + float w_sum = 0.f; + + for (int i = 0; i < 12; ++i) { + w_sum += gauss(centers[i], 0.f); + } + + const auto process_pixel = + [&](float y) -> float { + // convert to log space + const float luma = rtengine::max(log2(rtengine::max(y, 0.f)), -18.0f); + + // build the correction as the sum of the contribution of each + // luminance channel to current pixel + float correction = 0.0f; + + for (int c = 0; c < 12; ++c) + { + correction += gauss(centers[c], luma) * factors[c]; + } + + correction /= w_sum; + + return correction; + }; + + std::vector> cur_colormap; + if (show_color_map) { + lcmsMutex->lock(); + cmsHPROFILE in = ICCStore::getInstance()->getsRGBProfile(); + cmsHPROFILE out = ICCStore::getInstance()->workingSpace(workingProfile); + cmsHTRANSFORM xform = cmsCreateTransform(in, TYPE_RGB_FLT, out, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE); + lcmsMutex->unlock(); + + for (auto &c : colormap) { + cur_colormap.push_back(c); + auto &cc = cur_colormap.back(); + cmsDoTransform(xform, &cc[0], &cc[0], 1); + } + + cmsDeleteTransform(xform); + } + + const auto process_colormap = + [&](float y) -> std::array + { + std::array ret = { 0.f, 0.f, 0.f }; + + // convert to log space + const float luma = max(log2(max(y, 0.f)), -18.0f); + + // build the correction as the sum of the contribution of each + // luminance channel to current pixel + for (int c = 0; c < 12; ++c) { + float w = gauss(centers[c], luma); + for (int i = 0; i < 3; ++i) { + ret[i] += w * cur_colormap[c][i]; + } + } + for (int i = 0; i < 3; ++i) { + ret[i] = LIM01(ret[i] / w_sum); + } + + return ret; + }; + + +#ifdef __SSE2__ + vfloat vfactors[12]; + vfloat vcenters[12]; + + for (int i = 0; i < 12; ++i) { + vfactors[i] = F2V(factors[i]); + vcenters[i] = F2V(centers[i]); + } + + const auto vgauss = + [](vfloat b, vfloat x) -> vfloat { + static const vfloat fourv = F2V(4.f); + return xexpf((-SQR(x - b) / fourv)); + }; + + vfloat zerov = F2V(0.f); + vfloat vw_sum = F2V(w_sum); + + const vfloat noisev = F2V(-18.f); + const vfloat xlog2v = F2V(xlogf(2.f)); + + const auto vprocess_pixel = + [&](vfloat y) -> vfloat { + const vfloat luma = vmaxf(xlogf(vmaxf(y, zerov)) / xlog2v, noisev); + + vfloat correction = zerov; + + for (int c = 0; c < 12; ++c) + { + correction += vgauss(vcenters[c], luma) * vfactors[c]; + } + + correction /= vw_sum; + + return correction; + }; + + + vfloat v1 = F2V(1.f); + vfloat v65535 = F2V(65535.f); +#endif // __SSE2__ + + + if (show_color_map) { + LUTf lut_r(65537), lut_g(65537), lut_b(65537); + for (int i = 0; i < 65536; ++i) { + float y = float(i)/65535.f; + auto rgb = process_colormap(y); + lut_r[i] = rgb[0]; + lut_g[i] = rgb[1]; + lut_b[i] = rgb[2]; + } + lut_r[65536] = cur_colormap.back()[0]; + lut_g[65536] = cur_colormap.back()[1]; + lut_b[65536] = cur_colormap.back()[2]; + +#ifdef _OPENMP +# pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float cY = Y[y][x] * 65535.f; + R[y][x] = lut_r[cY]; + G[y][x] = lut_g[cY]; + B[y][x] = lut_b[cY]; + } + } + return; + } + + + LUTf lut(65536); + + for (int i = 0; i < 65536; ++i) { + float y = float(i) / 65535.f; + float c = process_pixel(y); + lut[i] = c; + } + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + int x = 0; + + +#ifdef __SSE2__ + + for (; x < W - 3; x += 4) { + vfloat cY = LVFU(Y[y][x]); + vmask m = vmaskf_gt(cY, v1); + vfloat corr; + + if (_mm_movemask_ps((vfloat)m)) { + corr = vprocess_pixel(cY); + } else { + corr = lut[cY * v65535]; + } + + STVF(R[y][x], LVF(R[y][x]) * corr); + STVF(G[y][x], LVF(G[y][x]) * corr); + STVF(B[y][x], LVF(B[y][x]) * corr); + } + +#endif // __SSE2__ + + for (; x < W; ++x) { + float cY = Y[y][x]; + float corr = cY > 1.f ? process_pixel(cY) : lut[cY * 65535.f]; + R[y][x] *= corr; + G[y][x] *= corr; + B[y][x] *= corr; + } + } + +} + +bool ImProcFunctions::toneEqualizer(Imagefloat *rgb) +{ + if (!params->toneEqualizer.enabled) { + return false; + } + + BENCHFUN + + const float gain = 1.f / 65535.f * std::pow(2.f, -params->toneEqualizer.pivot); + + rgb->multiply(gain, multiThread); + + const int W = rgb->getWidth(); + const int H = rgb->getHeight(); + + array2D R(W, H, rgb->r.ptrs, ARRAY2D_BYREFERENCE); + array2D G(W, H, rgb->g.ptrs, ARRAY2D_BYREFERENCE); + array2D B(W, H, rgb->b.ptrs, ARRAY2D_BYREFERENCE); + + bool show_color_map = params->toneEqualizer.show_colormap; + toneEqualizer(R, G, B, params->toneEqualizer, params->icm.workingProfile, scale, multiThread, show_color_map); + + rgb->multiply(1.f/gain, multiThread); + + return show_color_map; +} + +} diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 9a9e4fef1..555af6879 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1786,6 +1786,30 @@ bool SHParams::operator !=(const SHParams& other) const return !(*this == other); } +ToneEqualizerParams::ToneEqualizerParams() : + enabled(false), + bands{0, 0, 0, 0, 0}, + regularization(4), + show_colormap(false), + pivot(0) +{ +} + +bool ToneEqualizerParams::operator ==(const ToneEqualizerParams &other) const +{ + return + enabled == other.enabled + && bands == other.bands + && regularization == other.regularization + && show_colormap == other.show_colormap + && pivot == other.pivot; +} + +bool ToneEqualizerParams::operator !=(const ToneEqualizerParams &other) const +{ + return !(*this == other); +} + CropParams::CropParams() : enabled(false), x(-1), @@ -5814,6 +5838,8 @@ void ProcParams::setDefaults() sh = {}; + toneEqualizer = {}; + crop = {}; coarse = {}; @@ -6241,6 +6267,14 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->sh.radius, "Shadows & Highlights", "Radius", sh.radius, keyFile); saveToKeyfile(!pedited || pedited->sh.lab, "Shadows & Highlights", "Lab", sh.lab, keyFile); +// Tone equalizer + saveToKeyfile(!pedited || pedited->toneEqualizer.enabled, "ToneEqualizer", "Enabled", toneEqualizer.enabled, keyFile); + for (size_t i = 0; i < toneEqualizer.bands.size(); ++i) { + saveToKeyfile(!pedited || pedited->toneEqualizer.bands, "ToneEqualizer", "Band" + std::to_string(i), toneEqualizer.bands[i], keyFile); + } + saveToKeyfile(!pedited || pedited->toneEqualizer.regularization, "ToneEqualizer", "Regularization", toneEqualizer.regularization, keyFile); + saveToKeyfile(!pedited || pedited->toneEqualizer.pivot, "ToneEqualizer", "Pivot", toneEqualizer.pivot, keyFile); + // Crop saveToKeyfile(!pedited || pedited->crop.enabled, "Crop", "Enabled", crop.enabled, keyFile); saveToKeyfile(!pedited || pedited->crop.x, "Crop", "X", crop.x, keyFile); @@ -8226,6 +8260,15 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } } + if (keyFile.has_group("ToneEqualizer")) { + assignFromKeyfile(keyFile, "ToneEqualizer", "Enabled", pedited, toneEqualizer.enabled, pedited->toneEqualizer.enabled); + for (size_t i = 0; i < toneEqualizer.bands.size(); ++i) { + assignFromKeyfile(keyFile, "ToneEqualizer", "Band" + std::to_string(i), pedited, toneEqualizer.bands[i], pedited->toneEqualizer.bands); + } + assignFromKeyfile(keyFile, "ToneEqualizer", "Regularization", pedited, toneEqualizer.regularization, pedited->toneEqualizer.regularization); + assignFromKeyfile(keyFile, "ToneEqualizer", "Pivot", pedited, toneEqualizer.pivot, pedited->toneEqualizer.pivot); + } + if (keyFile.has_group("Crop")) { assignFromKeyfile(keyFile, "Crop", "Enabled", pedited, crop.enabled, pedited->crop.enabled); assignFromKeyfile(keyFile, "Crop", "X", pedited, crop.x, pedited->crop.x); @@ -10447,6 +10490,7 @@ bool ProcParams::operator ==(const ProcParams& other) const && fattal == other.fattal && defringe == other.defringe && sh == other.sh + && toneEqualizer == other.toneEqualizer && crop == other.crop && coarse == other.coarse && rotate == other.rotate diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 04229867b..5a86b40a0 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -827,6 +827,22 @@ struct SHParams { bool operator !=(const SHParams& other) const; }; +/** + * Tone equalizer parameters. + */ +struct ToneEqualizerParams { + bool enabled; + std::array bands; + int regularization; + bool show_colormap; + double pivot; + + ToneEqualizerParams(); + + bool operator ==(const ToneEqualizerParams &other) const; + bool operator !=(const ToneEqualizerParams &other) const; +}; + /** * Parameters of the cropping */ @@ -2547,6 +2563,7 @@ public: EPDParams epd; ///< Edge Preserving Decomposition parameters FattalToneMappingParams fattal; ///< Fattal02 tone mapping SHParams sh; ///< Shadow/highlight enhancement parameters + ToneEqualizerParams toneEqualizer; ///< Tone equalizer parameters CropParams crop; ///< Crop parameters CoarseTransformParams coarse; ///< Coarse transformation (90, 180, 270 deg rotation, h/v flipping) parameters CommonTransformParams commonTrans; ///< Common transformation parameters (autofill) diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 7976bdc7a..1d56f2638 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -159,6 +159,7 @@ set(NONCLISOURCEFILES thumbimageupdater.cc thumbnail.cc tonecurve.cc + toneequalizer.cc toolbar.cc toolpanel.cc toolpanelcoord.cc diff --git a/rtgui/adjuster.cc b/rtgui/adjuster.cc index a2f96cac3..3fe3d61eb 100644 --- a/rtgui/adjuster.cc +++ b/rtgui/adjuster.cc @@ -51,6 +51,7 @@ Adjuster::Adjuster( grid(nullptr), label(nullptr), imageIcon1(imgIcon1), + imageIcon2(imgIcon2), automatic(nullptr), adjusterListener(nullptr), spinChange(options.adjusterMinDelay, options.adjusterMaxDelay), @@ -76,8 +77,8 @@ Adjuster::Adjuster( setExpandAlignProperties(imageIcon1, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); } - if (imgIcon2) { - setExpandAlignProperties(imgIcon2, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); + if (imageIcon2) { + setExpandAlignProperties(imageIcon2, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER); } set_column_spacing(0); @@ -120,9 +121,9 @@ Adjuster::Adjuster( attach_next_to(*imageIcon1, *slider, Gtk::POS_LEFT, 1, 1); } - if (imgIcon2) { - attach_next_to(*imgIcon2, *slider, Gtk::POS_RIGHT, 1, 1); - attach_next_to(*spin, *imgIcon2, Gtk::POS_RIGHT, 1, 1); + if (imageIcon2) { + attach_next_to(*imageIcon2, *slider, Gtk::POS_RIGHT, 1, 1); + attach_next_to(*spin, *imageIcon2, Gtk::POS_RIGHT, 1, 1); } else { attach_next_to(*spin, *slider, Gtk::POS_RIGHT, 1, 1); } @@ -140,9 +141,9 @@ Adjuster::Adjuster( grid->attach_next_to(*imageIcon1, *slider, Gtk::POS_LEFT, 1, 1); } - if (imgIcon2) { - grid->attach_next_to(*imgIcon2, Gtk::POS_RIGHT, 1, 1); - grid->attach_next_to(*reset, *imgIcon2, Gtk::POS_RIGHT, 1, 1); + if (imageIcon2) { + grid->attach_next_to(*imageIcon2, Gtk::POS_RIGHT, 1, 1); + grid->attach_next_to(*reset, *imageIcon2, Gtk::POS_RIGHT, 1, 1); } else { grid->attach_next_to(*reset, *slider, Gtk::POS_RIGHT, 1, 1); } @@ -683,3 +684,13 @@ void Adjuster::setDelay(unsigned int min_delay_ms, unsigned int max_delay_ms) spinChange.setDelay(min_delay_ms, max_delay_ms); sliderChange.setDelay(min_delay_ms, max_delay_ms); } + +void Adjuster::showIcons(bool yes) +{ + if (imageIcon1) { + imageIcon1->set_visible(yes); + } + if (imageIcon2) { + imageIcon2->set_visible(yes); + } +} diff --git a/rtgui/adjuster.h b/rtgui/adjuster.h index abafbd730..76b46b094 100644 --- a/rtgui/adjuster.h +++ b/rtgui/adjuster.h @@ -41,6 +41,7 @@ protected: Gtk::Grid* grid; Gtk::Label* label; Gtk::Image *imageIcon1; + Gtk::Image *imageIcon2; MyHScale* slider; MySpinButton* spin; Gtk::Button* reset; @@ -133,4 +134,5 @@ public: void trimValue (int &val) const; void setLogScale(double base, double pivot, bool anchorMiddle = false); void setDelay(unsigned int min_delay_ms, unsigned int max_delay_ms = 0); + void showIcons(bool yes); }; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 529d57da6..ff48a58b1 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -317,6 +317,11 @@ void ParamsEdited::set(bool v) sh.stonalwidth = v; sh.radius = v; sh.lab = v; + toneEqualizer.enabled = v; + toneEqualizer.bands = v; + toneEqualizer.regularization = v; + toneEqualizer.show_colormap = v; + toneEqualizer.pivot = v; crop.enabled = v; crop.x = v; crop.y = v; @@ -1031,6 +1036,11 @@ void ParamsEdited::initFrom(const std::vector& crop.ratio = crop.ratio && p.crop.ratio == other.crop.ratio; crop.orientation = crop.orientation && p.crop.orientation == other.crop.orientation; crop.guide = crop.guide && p.crop.guide == other.crop.guide; + toneEqualizer.enabled = toneEqualizer.enabled && p.toneEqualizer.enabled == other.toneEqualizer.enabled; + toneEqualizer.bands = toneEqualizer.bands && p.toneEqualizer.bands == other.toneEqualizer.bands; + toneEqualizer.regularization = toneEqualizer.regularization && p.toneEqualizer.regularization == other.toneEqualizer.regularization; + toneEqualizer.show_colormap = toneEqualizer.show_colormap && p.toneEqualizer.show_colormap == other.toneEqualizer.show_colormap; + toneEqualizer.pivot = toneEqualizer.pivot && p.toneEqualizer.pivot == other.toneEqualizer.pivot; coarse.rotate = coarse.rotate && p.coarse.rotate == other.coarse.rotate; coarse.hflip = coarse.hflip && p.coarse.hflip == other.coarse.hflip; coarse.vflip = coarse.vflip && p.coarse.vflip == other.coarse.vflip; @@ -3194,6 +3204,26 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.sh.lab = mods.sh.lab; } + if (toneEqualizer.enabled) { + toEdit.toneEqualizer.enabled = mods.toneEqualizer.enabled; + } + + if (toneEqualizer.bands) { + toEdit.toneEqualizer.bands = mods.toneEqualizer.bands; + } + + if (toneEqualizer.regularization) { + toEdit.toneEqualizer.regularization = mods.toneEqualizer.regularization; + } + + if (toneEqualizer.show_colormap) { + toEdit.toneEqualizer.show_colormap = mods.toneEqualizer.show_colormap; + } + + if (toneEqualizer.pivot) { + toEdit.toneEqualizer.pivot = mods.toneEqualizer.pivot; + } + if (crop.enabled) { toEdit.crop.enabled = mods.crop.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 94abed470..d70336a54 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -358,6 +358,14 @@ struct SHParamsEdited { bool lab; }; +struct ToneEqualizerParamsEdited { + bool enabled; + bool bands; + bool regularization; + bool show_colormap; + bool pivot; +}; + struct CropParamsEdited { bool enabled; bool x; @@ -1545,6 +1553,7 @@ struct ParamsEdited { FattalToneMappingParamsEdited fattal; ImpulseDenoiseParamsEdited impulseDenoise; SHParamsEdited sh; + ToneEqualizerParamsEdited toneEqualizer; CropParamsEdited crop; CoarseTransformParamsEdited coarse; CommonTransformParamsEdited commonTrans; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index b3c49dfd7..08cf23f72 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -219,6 +219,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren wb = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_WHITEBALANCE"))); exposure = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_EXPOSURE"))); sh = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_SHADOWSHIGHLIGHTS"))); + toneEqualizer = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_TONE_EQUALIZER"))); epd = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_EPD"))); fattal = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_TM_FATTAL"))); pcvignette = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_PCVIGNETTE"))); @@ -331,6 +332,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[0]->pack_start (*wb, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*exposure, Gtk::PACK_SHRINK, 2); vboxes[0]->pack_start (*sh, Gtk::PACK_SHRINK, 2); + vboxes[0]->pack_start (*toneEqualizer, 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 (*pcvignette, Gtk::PACK_SHRINK, 2); @@ -496,6 +498,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren wbConn = wb->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); 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)); + toneEqualizerConn = toneEqualizer->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)); pcvignetteConn = pcvignette->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); @@ -701,6 +704,7 @@ void PartialPasteDlg::basicToggled () ConnectionBlocker wbBlocker(wbConn); ConnectionBlocker exposureBlocker(exposureConn); ConnectionBlocker shBlocker(shConn); + ConnectionBlocker toneEqualizerBlocker(toneEqualizerConn); ConnectionBlocker epdBlocker(epdConn); ConnectionBlocker fattalBlocker(fattalConn); ConnectionBlocker pcvignetteBlocker(pcvignetteConn); @@ -712,6 +716,7 @@ void PartialPasteDlg::basicToggled () wb->set_active (basic->get_active ()); exposure->set_active (basic->get_active ()); sh->set_active (basic->get_active ()); + toneEqualizer->set_active (basic->get_active ()); epd->set_active (basic->get_active ()); fattal->set_active (basic->get_active ()); pcvignette->set_active (basic->get_active ()); @@ -889,6 +894,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.sh = falsePE.sh; } + if (!toneEqualizer->get_active ()) { + filterPE.toneEqualizer = falsePE.toneEqualizer; + } + if (!epd->get_active ()) { filterPE.epd = falsePE.epd; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 19e1eb462..fc45bd5e4 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -133,6 +133,7 @@ public: Gtk::CheckButton* exposure; Gtk::CheckButton* localcontrast; Gtk::CheckButton* sh; + Gtk::CheckButton* toneEqualizer; Gtk::CheckButton* epd; Gtk::CheckButton* fattal; Gtk::CheckButton* retinex; @@ -225,6 +226,7 @@ public: sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaConn, rawConn, advancedConn; sigc::connection locallabConn; sigc::connection wbConn, exposureConn, localcontrastConn, shConn, pcvignetteConn, gradientConn, labcurveConn, colorappearanceConn; + sigc::connection toneEqualizerConn; sigc::connection spotConn, sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn, dehazeConn; sigc::connection vibranceConn, chmixerConn, hsveqConn, rgbcurvesConn, chmixerbwConn, colortoningConn, filmSimulationConn, softlightConn; sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index cc8e9ad81..01aba228f 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -462,6 +462,7 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh || pparams->epd != pp.epd || pparams->fattal != pp.fattal || pparams->sh != pp.sh + || pparams->toneEqualizer != pp.toneEqualizer || pparams->crop != pp.crop || pparams->coarse != pp.coarse || pparams->commonTrans != pp.commonTrans diff --git a/rtgui/toneequalizer.cc b/rtgui/toneequalizer.cc new file mode 100644 index 000000000..366376ea4 --- /dev/null +++ b/rtgui/toneequalizer.cc @@ -0,0 +1,169 @@ +/* + * Adapted from ART. + * + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 "eventmapper.h" +#include "toneequalizer.h" +#include "rtimage.h" + +using namespace rtengine; +using namespace rtengine::procparams; + + +ToneEqualizer::ToneEqualizer(): FoldableToolPanel(this, "toneequalizer", M("TP_TONE_EQUALIZER_LABEL"), false, true) +{ + auto m = ProcEventMapper::getInstance(); + EvEnabled = m->newEvent(RGBCURVE, "HISTORY_MSG_TONE_EQUALIZER_ENABLED"); + EvBands = m->newEvent(RGBCURVE, "HISTORY_MSG_TONE_EQUALIZER_BANDS"); + EvRegularization = m->newEvent(RGBCURVE, "HISTORY_MSG_TONE_EQUALIZER_REGULARIZATION"); + EvColormap = m->newEvent(RGBCURVE, "HISTORY_MSG_TONE_EQUALIZER_SHOW_COLOR_MAP"); + EvPivot = m->newEvent(RGBCURVE, "HISTORY_MSG_TONE_EQUALIZER_PIVOT"); + + std::array images = { + "purple", + "blue", + "gray", + "yellow", + "red" + }; + for (size_t i = 0; i < bands.size(); ++i) { + bands[i] = Gtk::manage(new Adjuster(M("TP_TONE_EQUALIZER_BAND_" + std::to_string(i)), -100, 100, 1, 0, Gtk::manage(new RTImage(Glib::ustring("circle-") + images[i] + "-small.png")))); + bands[i]->setAdjusterListener(this); + pack_start(*bands[i]); + bands[i]->showIcons(false); + } + + pivot = Gtk::manage(new Adjuster(M("TP_TONE_EQUALIZER_PIVOT"), -12, 12, 0.05, 0)); + pivot->setLogScale(64, 0, true); + pivot->setAdjusterListener(this); + pack_start(*pivot); + + pack_start(*Gtk::manage(new Gtk::HSeparator())); + regularization = Gtk::manage(new Adjuster(M("TP_TONE_EQUALIZER_DETAIL"), -5, 5, 1, 5)); + regularization->setAdjusterListener(this); + pack_start(*regularization); + + show_colormap = Gtk::manage(new Gtk::CheckButton(M("TP_TONE_EQUALIZER_SHOW_COLOR_MAP"))); + pack_start(*show_colormap); + show_colormap->signal_toggled().connect(sigc::mem_fun(this, &ToneEqualizer::colormapToggled)); + + show_all_children (); +} + + +void ToneEqualizer::read(const ProcParams *pp, const ParamsEdited* pedited) +{ + disableListener(); + + setEnabled(pp->toneEqualizer.enabled); + + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->setValue(pp->toneEqualizer.bands[i]); + bands[i]->showIcons(pp->toneEqualizer.show_colormap); + } + regularization->setValue(pp->toneEqualizer.regularization); + + pivot->setValue(pp->toneEqualizer.pivot); + show_colormap->set_active(pp->toneEqualizer.show_colormap); + + enableListener(); +} + + +void ToneEqualizer::write(ProcParams *pp, ParamsEdited* pedited) +{ + for (size_t i = 0; i < bands.size(); ++i) { + pp->toneEqualizer.bands[i] = bands[i]->getValue(); + } + pp->toneEqualizer.enabled = getEnabled(); + pp->toneEqualizer.regularization = regularization->getValue(); + pp->toneEqualizer.show_colormap = show_colormap->get_active(); + pp->toneEqualizer.pivot = pivot->getValue(); +} + + +void ToneEqualizer::setDefaults(const ProcParams *defParams, const ParamsEdited* pedited) +{ + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->setDefault(defParams->toneEqualizer.bands[i]); + } + regularization->setDefault(defParams->toneEqualizer.regularization); + + pivot->setDefault(defParams->toneEqualizer.pivot); + inital_params = defParams->toneEqualizer; +} + + +void ToneEqualizer::adjusterChanged(Adjuster *a, double newval) +{ + if (listener && getEnabled()) { + if (a == regularization) { + listener->panelChanged(EvRegularization, Glib::ustring::format(a->getValue())); + } else if (a == pivot) { + listener->panelChanged(EvPivot, Glib::ustring::format(a->getValue())); + } else { + Glib::ustring s; + for (size_t i = 0; i < bands.size(); ++i) { + s += Glib::ustring::format((int)bands[i]->getValue()) + " "; + } + listener->panelChanged(EvBands, s); + } + } +} + + +void ToneEqualizer::adjusterAutoToggled(Adjuster *a) +{ +} + + +void ToneEqualizer::enabledChanged() +{ + if (listener) { + if (get_inconsistent()) { + listener->panelChanged(EvEnabled, M("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged(EvEnabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(EvEnabled, M("GENERAL_DISABLED")); + } + } +} + + +void ToneEqualizer::colormapToggled() +{ + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->showIcons(show_colormap->get_active()); + } + if (listener && getEnabled()) { + listener->panelChanged(EvColormap, show_colormap->get_active() ? M("GENERAL_ENABLED") : M("GENERAL_DISABLED")); + } +} + + +void ToneEqualizer::trimValues(rtengine::procparams::ProcParams *pp) +{ + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->trimValue(pp->toneEqualizer.bands[i]); + } + regularization->trimValue(pp->toneEqualizer.regularization); + pivot->trimValue(pp->toneEqualizer.pivot); +} + diff --git a/rtgui/toneequalizer.h b/rtgui/toneequalizer.h new file mode 100644 index 000000000..f4fed79af --- /dev/null +++ b/rtgui/toneequalizer.h @@ -0,0 +1,56 @@ +/* -*- C++ -*- + * + * Adapted from ART. + * + * This file is part of RawTherapee. + * + * Copyright (c) 2004-2010 Gabor Horvath + * + * 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 ToneEqualizer: public ToolParamBlock, public AdjusterListener, public FoldableToolPanel { +public: + ToneEqualizer(); + + void read(const rtengine::procparams::ProcParams *pp, const ParamsEdited* pedited = nullptr) override; + void write(rtengine::procparams::ProcParams *pp, ParamsEdited* pedited = nullptr) override; + void setDefaults(const rtengine::procparams::ProcParams *defParams, const ParamsEdited* pedited = nullptr) override; + void adjusterChanged(Adjuster *a, double newval) override; + void adjusterAutoToggled(Adjuster *a) override; + void enabledChanged() override; + + void trimValues(rtengine::procparams::ProcParams *pp) override; + +private: + void colormapToggled(); + + std::array bands; + Adjuster *regularization; + Adjuster *pivot; + Gtk::CheckButton *show_colormap; + + rtengine::ProcEvent EvEnabled; + rtengine::ProcEvent EvBands; + rtengine::ProcEvent EvRegularization; + rtengine::ProcEvent EvColormap; + rtengine::ProcEvent EvPivot; + + rtengine::procparams::ToneEqualizerParams inital_params; +}; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 2c506710f..b61b059f6 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -47,6 +47,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit coarse = Gtk::manage (new CoarsePanel ()); toneCurve = Gtk::manage (new ToneCurve ()); shadowshighlights = Gtk::manage (new ShadowsHighlights ()); + toneEqualizer = Gtk::manage (new ToneEqualizer ()); impulsedenoise = Gtk::manage (new ImpulseDenoise ()); defringe = Gtk::manage (new Defringe ()); spot = Gtk::manage (new Spot ()); @@ -118,6 +119,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit addfavoritePanel (colorPanel, chmixer); addfavoritePanel (colorPanel, blackwhite); addfavoritePanel (exposurePanel, shadowshighlights); + addfavoritePanel (exposurePanel, toneEqualizer); addfavoritePanel (detailsPanel, spot); addfavoritePanel (detailsPanel, sharpening); addfavoritePanel (detailsPanel, localContrast); diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 13686d6e3..3f7afacc6 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -78,6 +78,7 @@ #include "softlight.h" #include "spot.h" #include "tonecurve.h" +#include "toneequalizer.h" #include "toolbar.h" #include "toolpanel.h" #include "vibrance.h" @@ -133,6 +134,7 @@ protected: Crop* crop; ToneCurve* toneCurve; ShadowsHighlights* shadowshighlights; + ToneEqualizer* toneEqualizer; LocalContrast *localContrast; Spot* spot; Defringe* defringe;