diff --git a/rtdata/languages/default b/rtdata/languages/default index 4c99d113b..050836d21 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1444,6 +1444,7 @@ HISTORY_MSG_LOCALCONTRAST_ENABLED;Local Contrast HISTORY_MSG_LOCALCONTRAST_LIGHTNESS;Local Contrast - Lightness HISTORY_MSG_LOCALCONTRAST_RADIUS;Local Contrast - Radius HISTORY_MSG_LOCAL_GAMUTMUNSEL;Local - Gamut-Munsell +HISTORY_MSG_LOCALLAB_TE_PIVOT;Local - Equalizer pivot HISTORY_MSG_METADATA_MODE;Metadata copy mode HISTORY_MSG_MICROCONTRAST_CONTRAST;Microcontrast - Contrast threshold HISTORY_MSG_PDSHARPEN_AUTO_CONTRAST;CS - Auto threshold @@ -1489,6 +1490,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 @@ -1796,6 +1802,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 @@ -3387,6 +3394,7 @@ TP_LOCALLAB_STYPE_TOOLTIP;You can choose between:\nSymmetrical - left handle lin TP_LOCALLAB_SYM;Symmetrical (mouse) TP_LOCALLAB_SYMSL;Symmetrical (mouse + sliders) TP_LOCALLAB_TARGET_GRAY;Mean luminance (Yb%) +TP_LOCALLAB_TE_PIVOT;Pivot (Ev) TP_LOCALLAB_THRES;Threshold structure TP_LOCALLAB_THRESDELTAE;ΔE scope threshold TP_LOCALLAB_THRESRETI;Threshold @@ -3783,6 +3791,16 @@ 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_BANDS;Bands +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 c657d6f9d..58c45f39b 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 d69df9325..38a4bf651 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -213,9 +213,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 2ab6383bf..3a3529131 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -2809,6 +2809,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 c3e11c076..3f9a426cf 100644 --- a/rtengine/improcfun.cc +++ b/rtengine/improcfun.cc @@ -1979,7 +1979,9 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer stop.reset(new StopWatch("rgb processing")); } - Imagefloat *tmpImage = nullptr; + const bool split_tiled_parts_1_2 = params->toneEqualizer.enabled; + + std::unique_ptr tmpImage; Imagefloat* editImgFloat = nullptr; PlanarWhateverData* editWhatever = nullptr; @@ -2139,6 +2141,7 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer const float comp = (max(0.0, expcomp) + 1.0) * hlcompr / 100.0; const float shoulder = ((65536.f / max(1.0f, exp_scale)) * (hlcomprthresh / 200.f)) + 0.1f; const float hlrange = 65536.f - shoulder; + const int tone_curve_black = params->toneCurve.black; const bool isProPhoto = (params->icm.workingProfile == "ProPhoto"); // extracting data from 'params' to avoid cache flush (to be confirmed) ToneCurveMode curveMode = params->toneCurve.curveMode; @@ -2247,8 +2250,8 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer } bool hasgammabw = gammabwr != 1.f || gammabwg != 1.f || gammabwb != 1.f; - if (hasColorToning || blackwhite || (params->dirpyrequalizer.cbdlMethod == "bef" && params->dirpyrequalizer.enabled)) { - tmpImage = new Imagefloat(working->getWidth(), working->getHeight()); + if (hasColorToning || blackwhite || (params->dirpyrequalizer.cbdlMethod == "bef" && params->dirpyrequalizer.enabled) || split_tiled_parts_1_2) { + tmpImage.reset(new Imagefloat(working->getWidth(), working->getHeight())); } // For tonecurve histogram @@ -2263,9 +2266,53 @@ 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])}; - #define TS 112 + const auto tiled_part_1 = + [working, + mixchannels, + &hltonecurve, &shtonecurve, + chMixRR, chMixRG, chMixRB, + chMixGR, chMixGG, chMixGB, + chMixBR, chMixBG, chMixBB, + exp_scale, comp, hlrange, tone_curve_black]( + int istart, int jstart, int tH, int tW, + float *rtemp, float *gtemp, float *btemp) { + + for (int i = istart, ti = 0; i < tH; i++, ti++) { + for (int j = jstart, tj = 0; j < tW; j++, tj++) { + rtemp[ti * TS + tj] = working->r(i, j); + gtemp[ti * TS + tj] = working->g(i, j); + btemp[ti * TS + tj] = working->b(i, j); + } + } + + if (mixchannels) { + for (int i = istart, ti = 0; i < tH; i++, ti++) { + for (int j = jstart, tj = 0; j < tW; j++, tj++) { + float r = rtemp[ti * TS + tj]; + float g = gtemp[ti * TS + tj]; + float b = btemp[ti * TS + tj]; + + // if (i==100 & j==100) printf("rgbProc input R= %f G= %f B= %f \n",r,g,b); + float rmix = (r * chMixRR + g * chMixRG + b * chMixRB) / 100.f; + float gmix = (r * chMixGR + g * chMixGG + b * chMixGB) / 100.f; + float bmix = (r * chMixBR + g * chMixBG + b * chMixBB) / 100.f; + + rtemp[ti * TS + tj] = rmix; + gtemp[ti * TS + tj] = gmix; + btemp[ti * TS + tj] = bmix; + } + } + } + + highlightToneCurve(hltonecurve, rtemp, gtemp, btemp, istart, tH, jstart, tW, TS, exp_scale, comp, hlrange); + + if (tone_curve_black != 0) { + shadowToneCurve(shtonecurve, rtemp, gtemp, btemp, istart, tH, jstart, tW, TS); + } + }; + #ifdef _OPENMP #pragma omp parallel if (multiThread) #endif @@ -2316,6 +2363,41 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer histToneCurveThr.clear(); } + if (split_tiled_parts_1_2) { + +#ifdef _OPENMP + #pragma omp for schedule(dynamic, chunkSize) collapse(2) +#endif + + for (int ii = 0; ii < working->getHeight(); ii += TS) { + for (int jj = 0; jj < working->getWidth(); jj += TS) { + istart = ii; + jstart = jj; + tH = min(ii + TS, working->getHeight()); + tW = min(jj + TS, working->getWidth()); + + + tiled_part_1(istart, jstart, tH, tW, rtemp, gtemp, btemp); + + // Copy tile to image. + for (int i = istart, ti = 0; i < tH; i++, ti++) { + for (int j = jstart, tj = 0; j < tW; j++, tj++) { + tmpImage->r(i, j) = rtemp[ti * TS + tj]; + tmpImage->g(i, j) = gtemp[ti * TS + tj]; + tmpImage->b(i, j) = btemp[ti * TS + tj]; + } + } + } + } + } + +#ifdef _OPENMP + #pragma omp single +#endif + if (params->toneEqualizer.enabled) { + toneEqualizer(tmpImage.get()); + } + #ifdef _OPENMP #pragma omp for schedule(dynamic, chunkSize) collapse(2) #endif @@ -2327,38 +2409,16 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer tH = min(ii + TS, working->getHeight()); tW = min(jj + TS, working->getWidth()); - - for (int i = istart, ti = 0; i < tH; i++, ti++) { - for (int j = jstart, tj = 0; j < tW; j++, tj++) { - rtemp[ti * TS + tj] = working->r(i, j); - gtemp[ti * TS + tj] = working->g(i, j); - btemp[ti * TS + tj] = working->b(i, j); - } - } - - if (mixchannels) { + if (split_tiled_parts_1_2) { for (int i = istart, ti = 0; i < tH; i++, ti++) { for (int j = jstart, tj = 0; j < tW; j++, tj++) { - float r = rtemp[ti * TS + tj]; - float g = gtemp[ti * TS + tj]; - float b = btemp[ti * TS + tj]; - - //if (i==100 & j==100) printf("rgbProc input R= %f G= %f B= %f \n",r,g,b); - float rmix = (r * chMixRR + g * chMixRG + b * chMixRB) / 100.f; - float gmix = (r * chMixGR + g * chMixGG + b * chMixGB) / 100.f; - float bmix = (r * chMixBR + g * chMixBG + b * chMixBB) / 100.f; - - rtemp[ti * TS + tj] = rmix; - gtemp[ti * TS + tj] = gmix; - btemp[ti * TS + tj] = bmix; + rtemp[ti * TS + tj] = tmpImage->r(i, j); + gtemp[ti * TS + tj] = tmpImage->g(i, j); + btemp[ti * TS + tj] = tmpImage->b(i, j); } } - } - - highlightToneCurve(hltonecurve, rtemp, gtemp, btemp, istart, tH, jstart, tW, TS, exp_scale, comp, hlrange); - - if (params->toneCurve.black != 0.0) { - shadowToneCurve(shtonecurve, rtemp, gtemp, btemp, istart, tH, jstart, tW, TS); + } else { + tiled_part_1(istart, jstart, tH, tW, rtemp, gtemp, btemp); } if (dcpProf) { @@ -3509,10 +3569,6 @@ void ImProcFunctions::rgbProc(Imagefloat* working, LabImage* lab, PipetteBuffer } - if (tmpImage) { - delete tmpImage; - } - if (hCurveEnabled) { delete hCurve; } diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 8b05a7bb4..51d9c8ac1 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; @@ -493,7 +494,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); - + void toneEqualizer(Imagefloat *rgb); + void toneEqualizer(Imagefloat *rgb, const procparams::ToneEqualizerParams ¶ms, const Glib::ustring &workingProfile, double scale, bool multiThread); void softLight(LabImage *lab, const procparams::SoftLightParams &softLightParams); void labColorCorrectionRegions(LabImage *lab); diff --git a/rtengine/iplocallab.cc b/rtengine/iplocallab.cc index adc762710..e58c38faa 100644 --- a/rtengine/iplocallab.cc +++ b/rtengine/iplocallab.cc @@ -703,6 +703,7 @@ struct local_params { float mulloc[6]; int mullocsh[5]; int detailsh; + double tePivot; float threshol; float chromacb; float strengt; @@ -1700,6 +1701,7 @@ static void calcLocalParams(int sp, int oW, int oH, const LocallabParams& locall lp.detailsh = locallab.spots.at(sp).detailSH; + lp.tePivot = locallab.spots.at(sp).tePivot; lp.threshol = thresho; lp.chromacb = chromcbdl; lp.expvib = locallab.spots.at(sp).expvibrance && lp.activspot ; @@ -2267,231 +2269,14 @@ 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 . -*/ - +void tone_eq(ImProcFunctions *ipf, Imagefloat *rgb, const struct local_params &lp, const Glib::ustring &workingProfile, double scale, bool multithread) { - // 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.enabled = true; + params.regularization = lp.detailsh; + params.pivot = lp.tePivot; + std::copy(lp.mullocsh, lp.mullocsh + params.bands.size(), params.bands.begin()); + ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread); } void ImProcFunctions::loccont(int bfw, int bfh, LabImage* tmp1, float rad, float stren, int sk) { @@ -7815,12 +7600,7 @@ void ImProcFunctions::InverseColorLight_Local(bool tonequ, bool tonecurv, int sp } if (tonequ) { - tmpImage->normalizeFloatTo1(); - array2D Rtemp(GW, GH, tmpImage->r.ptrs, ARRAY2D_BYREFERENCE); - array2D Gtemp(GW, GH, tmpImage->g.ptrs, ARRAY2D_BYREFERENCE); - array2D Btemp(GW, GH, tmpImage->b.ptrs, ARRAY2D_BYREFERENCE); - tone_eq(Rtemp, Gtemp, Btemp, lp, params->icm.workingProfile, sk, multiThread); - tmpImage->normalizeFloatTo65535(); + tone_eq(this, tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread); } rgb2lab(*tmpImage, *temp, params->icm.workingProfile); @@ -16019,12 +15799,7 @@ void ImProcFunctions::Lab_Local( } if (tonequ) { - tmpImage->normalizeFloatTo1(); - array2D Rtemp(bfw, bfh, tmpImage->r.ptrs, ARRAY2D_BYREFERENCE); - array2D Gtemp(bfw, bfh, tmpImage->g.ptrs, ARRAY2D_BYREFERENCE); - array2D Btemp(bfw, bfh, tmpImage->b.ptrs, ARRAY2D_BYREFERENCE); - tone_eq(Rtemp, Gtemp, Btemp, lp, params->icm.workingProfile, scal, multiThread); - tmpImage->normalizeFloatTo65535(); + tone_eq(this, tmpImage, lp, params->icm.workingProfile, scal, multiThread); } rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile); diff --git a/rtengine/iptoneequalizer.cc b/rtengine/iptoneequalizer.cc new file mode 100644 index 000000000..7fb6c791f --- /dev/null +++ b/rtengine/iptoneequalizer.cc @@ -0,0 +1,373 @@ +#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}, // 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}, + {1.f, 0.f, 0.f} +}; + + +void toneEqualizer( + array2D &R, array2D &G, array2D &B, + const rtengine::ToneEqualizerParams ¶ms, + 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] = { + -16.0f, -14.0f, -12.0f, -10.0f, -8.0f, -6.0f, + -4.0f, -2.0f, 0.0f, 2.0f, 4.0f, 6.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), // -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 + conv(params.bands[4], 3.f, 2.f) // 6 EV + }; + + rtengine::TMatrix ws = rtengine::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] = rtengine::Color::rgbLuminance(R[y][x], G[y][x], B[y][x], ws); + } + } + + int detail = rtengine::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 = rtengine::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((-rtengine::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); + } + + constexpr float luma_lo = -14.f; + constexpr float luma_hi = 4.f; + + const auto process_pixel = + [&](float y) -> float { + // convert to log space + const float luma = rtengine::LIM(log2(rtengine::max(y, 0.f)), luma_lo, luma_hi); + + // 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 (params.show_colormap) { + rtengine::lcmsMutex->lock(); + cmsHPROFILE in = rtengine::ICCStore::getInstance()->getsRGBProfile(); + cmsHPROFILE out = rtengine::ICCStore::getInstance()->workingSpace(workingProfile); + cmsHTRANSFORM xform = cmsCreateTransform(in, TYPE_RGB_FLT, out, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOOPTIMIZE | cmsFLAGS_NOCACHE); + rtengine::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 = rtengine::LIM(log2(rtengine::max(y, 0.f)), luma_lo, luma_hi); + + // 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] = rtengine::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((-rtengine::SQR(x - b) / fourv)); + }; + + vfloat zerov = F2V(0.f); + vfloat vw_sum = F2V(w_sum); + + const vfloat vluma_lo = F2V(luma_lo); + const vfloat vluma_hi = F2V(luma_hi); + const vfloat xlog2v = F2V(xlogf(2.f)); + + const auto vprocess_pixel = + [&](vfloat y) -> vfloat { + const vfloat luma = vminf(vmaxf(xlogf(vmaxf(y, zerov)) / xlog2v, vluma_lo), vluma_hi); + + 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 (params.show_colormap) { + 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; + } + } + +} + +} + + +namespace rtengine +{ + +void ImProcFunctions::toneEqualizer( + Imagefloat *rgb, + const ToneEqualizerParams ¶ms, + const Glib::ustring &workingProfile, + double scale, + bool multiThread) +{ + if (!params.enabled) { + return; + } + + BENCHFUN + + const float gain = 1.f / 65535.f * std::pow(2.f, -params.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); + + ::toneEqualizer(R, G, B, params, workingProfile, scale, multiThread); + + rgb->multiply(params.show_colormap ? 65535.f : 1.f/gain, multiThread); +} + +void ImProcFunctions::toneEqualizer(Imagefloat *rgb) +{ + toneEqualizer(rgb, params->toneEqualizer, params->icm.workingProfile, scale, multiThread); +} + +} diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 8b1dbaefd..a421be9b3 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1866,6 +1866,30 @@ bool SHParams::operator !=(const SHParams& other) const return !(*this == other); } +ToneEqualizerParams::ToneEqualizerParams() : + enabled(false), + bands{0, 0, 0, 0, 0}, + regularization(0), + 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), @@ -3381,6 +3405,7 @@ LocallabParams::LocallabSpot::LocallabSpot() : slomaskSH(0.0), lapmaskSH(0.0), detailSH(0), + tePivot(0.), reparsh(100.), LmaskSHcurve{ static_cast(DCT_NURBS), @@ -4825,6 +4850,7 @@ bool LocallabParams::LocallabSpot::operator ==(const LocallabSpot& other) const && slomaskSH == other.slomaskSH && lapmaskSH == other.lapmaskSH && detailSH == other.detailSH + && tePivot == other.tePivot && reparsh == other.reparsh && LmaskSHcurve == other.LmaskSHcurve && fatamountSH == other.fatamountSH @@ -5896,6 +5922,8 @@ void ProcParams::setDefaults() sh = {}; + toneEqualizer = {}; + crop = {}; coarse = {}; @@ -6334,6 +6362,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[i], "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); @@ -6620,6 +6656,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || spot_edited->gammaskSH, "Locallab", "GammaskSH_" + index_str, spot.gammaskSH, keyFile); saveToKeyfile(!pedited || spot_edited->slomaskSH, "Locallab", "SlomaskSH_" + index_str, spot.slomaskSH, keyFile); saveToKeyfile(!pedited || spot_edited->detailSH, "Locallab", "DetailSH_" + index_str, spot.detailSH, keyFile); + saveToKeyfile(!pedited || spot_edited->tePivot, "Locallab", "TePivot_" + index_str, spot.tePivot, keyFile); saveToKeyfile(!pedited || spot_edited->reparsh, "Locallab", "Reparsh_" + index_str, spot.reparsh, keyFile); saveToKeyfile(!pedited || spot_edited->LmaskSHcurve, "Locallab", "LmaskSHCurve_" + index_str, spot.LmaskSHcurve, keyFile); saveToKeyfile(!pedited || spot_edited->fatamountSH, "Locallab", "FatamountSH_" + index_str, spot.fatamountSH, keyFile); @@ -8372,6 +8409,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[i]); + } + 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); @@ -8779,6 +8825,7 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Locallab", "SlomaskSH_" + index_str, pedited, spot.slomaskSH, spotEdited.slomaskSH); assignFromKeyfile(keyFile, "Locallab", "LapmaskSH_" + index_str, pedited, spot.lapmaskSH, spotEdited.lapmaskSH); assignFromKeyfile(keyFile, "Locallab", "DetailSH_" + index_str, pedited, spot.detailSH, spotEdited.detailSH); + assignFromKeyfile(keyFile, "Locallab", "TePivot_" + index_str, pedited, spot.tePivot, spotEdited.tePivot); assignFromKeyfile(keyFile, "Locallab", "Reparsh_" + index_str, pedited, spot.reparsh, spotEdited.reparsh); assignFromKeyfile(keyFile, "Locallab", "LmaskSHCurve_" + index_str, pedited, spot.LmaskSHcurve, spotEdited.LmaskSHcurve); assignFromKeyfile(keyFile, "Locallab", "FatamountSH_" + index_str, pedited, spot.fatamountSH, spotEdited.fatamountSH); @@ -10615,6 +10662,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 48b68f598..6be2be54c 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -18,6 +18,7 @@ */ #pragma once +#include #include #include #include @@ -840,6 +841,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 */ @@ -1210,6 +1227,7 @@ struct LocallabParams { double slomaskSH; double lapmaskSH; int detailSH; + double tePivot; double reparsh; std::vector LmaskSHcurve; double fatamountSH; @@ -2562,6 +2580,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 7f5c2dfe3..bd08160ae 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -161,6 +161,7 @@ set(NONCLISOURCEFILES thumbimageupdater.cc thumbnail.cc tonecurve.cc + toneequalizer.cc toolbar.cc toollocationpref.cc toolpanel.cc diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 31097931a..d0d24fa76 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -10,6 +10,9 @@ enum { ADDSET_SH_HIGHLIGHTS, ADDSET_SH_SHADOWS, ADDSET_SH_LOCALCONTRAST, // not used anymore + ADDSET_TONE_EQUALIZER_BANDS, + ADDSET_TONE_EQUALIZER_PIVOT, + ADDSET_TONE_EQUALIZER_REGULARIZATION, ADDSET_LC_BRIGHTNESS, ADDSET_LC_CONTRAST, ADDSET_SHARP_AMOUNT, diff --git a/rtgui/adjuster.cc b/rtgui/adjuster.cc index 2beb429a8..49a5ed21b 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,15 @@ 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); + imageIcon1->set_no_show_all(!yes); + } + if (imageIcon2) { + imageIcon2->set_visible(yes); + imageIcon2->set_no_show_all(!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/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 297608896..4383ccb74 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -219,6 +219,7 @@ void BatchToolPanelCoordinator::initSession () chmixer->setAdjusterBehavior (options.baBehav[ADDSET_CHMIXER] ); blackwhite->setAdjusterBehavior (options.baBehav[ADDSET_BLACKWHITE_HUES], options.baBehav[ADDSET_BLACKWHITE_GAMMA]); shadowshighlights->setAdjusterBehavior (options.baBehav[ADDSET_SH_HIGHLIGHTS], options.baBehav[ADDSET_SH_SHADOWS]); + toneEqualizer->setAdjusterBehavior(options.baBehav[ADDSET_TONE_EQUALIZER_BANDS], options.baBehav[ADDSET_TONE_EQUALIZER_REGULARIZATION], options.baBehav[ADDSET_TONE_EQUALIZER_PIVOT]); dirpyrequalizer->setAdjusterBehavior (options.baBehav[ADDSET_DIRPYREQ], options.baBehav[ADDSET_DIRPYREQ_THRESHOLD], options.baBehav[ADDSET_DIRPYREQ_SKINPROTECT]); wavelet->setAdjusterBehavior (options.baBehav[ADDSET_WA], options.baBehav[ADDSET_WA_THRESHOLD], options.baBehav[ADDSET_WA_THRESHOLD2], options.baBehav[ADDSET_WA_THRES], options.baBehav[ADDSET_WA_CHRO], options.baBehav[ADDSET_WA_CHROMA], options.baBehav[ADDSET_WA_CONTRAST], options.baBehav[ADDSET_WA_SKINPROTECT], options.baBehav[ADDSET_WA_RESCHRO], options.baBehav[ADDSET_WA_TMRS], options.baBehav[ADDSET_WA_EDGS], options.baBehav[ADDSET_WA_SCALE], options.baBehav[ADDSET_WA_RESCON], options.baBehav[ADDSET_WA_RESCONH], options.baBehav[ADDSET_WA_THRR], options.baBehav[ADDSET_WA_THRRH], options.baBehav[ADDSET_WA_RADIUS], options.baBehav[ADDSET_WA_SKYPROTECT], options.baBehav[ADDSET_WA_EDGRAD], options.baBehav[ADDSET_WA_EDGVAL], options.baBehav[ADDSET_WA_STRENGTH], options.baBehav[ADDSET_WA_GAMMA], options.baBehav[ADDSET_WA_EDGEDETECT], options.baBehav[ADDSET_WA_EDGEDETECTTHR], options.baBehav[ADDSET_WA_EDGEDETECTTHR2]); dirpyrdenoise->setAdjusterBehavior (options.baBehav[ADDSET_DIRPYRDN_LUMA], options.baBehav[ADDSET_DIRPYRDN_LUMDET], options.baBehav[ADDSET_DIRPYRDN_CHROMA], options.baBehav[ADDSET_DIRPYRDN_CHROMARED], options.baBehav[ADDSET_DIRPYRDN_CHROMABLUE], options.baBehav[ADDSET_DIRPYRDN_GAMMA], options.baBehav[ADDSET_DIRPYRDN_PASSES]); @@ -242,6 +243,9 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_TC_SATURATION]) { pparams.toneCurve.saturation = 0;} if (options.baBehav[ADDSET_SH_HIGHLIGHTS]) { pparams.sh.highlights = 0; } if (options.baBehav[ADDSET_SH_SHADOWS]) { pparams.sh.shadows = 0; } + if (options.baBehav[ADDSET_TONE_EQUALIZER_BANDS]) { pparams.toneEqualizer.bands = {}; } + if (options.baBehav[ADDSET_TONE_EQUALIZER_PIVOT]) { pparams.toneEqualizer.pivot = 0; } + if (options.baBehav[ADDSET_TONE_EQUALIZER_REGULARIZATION]) { pparams.toneEqualizer.regularization = 0; } if (options.baBehav[ADDSET_LC_BRIGHTNESS]) { pparams.labCurve.brightness = 0; } if (options.baBehav[ADDSET_LC_CONTRAST]) { pparams.labCurve.contrast = 0; } if (options.baBehav[ADDSET_LC_CHROMATICITY]) { pparams.labCurve.chromaticity = 0; } diff --git a/rtgui/locallabtools.cc b/rtgui/locallabtools.cc index 2ebac1630..8bf88724c 100644 --- a/rtgui/locallabtools.cc +++ b/rtgui/locallabtools.cc @@ -3977,6 +3977,7 @@ LocallabShadow::LocallabShadow(): } ()), detailSH(Gtk::manage(new Adjuster(M("TP_LOCALLAB_DETAILSH"), -5, 5, 1, 0))), + tePivot(Gtk::manage(new Adjuster(M("TP_LOCALLAB_TE_PIVOT"), -12, 12, 0.05, 0))), highlights(Gtk::manage(new Adjuster(M("TP_SHADOWSHLIGHTS_HIGHLIGHTS"), 0, 100, 1, 0))), h_tonalwidth(Gtk::manage(new Adjuster(M("TP_SHADOWSHLIGHTS_HLTONALW"), 10, 100, 1, 70))), shadows(Gtk::manage(new Adjuster(M("TP_SHADOWSHLIGHTS_SHADOWS"), 0, 100, 1, 0))), @@ -4017,7 +4018,8 @@ LocallabShadow::LocallabShadow(): LmaskSHshape(static_cast(mask2SHCurveEditorG->addCurve(CT_Diagonal, "L(L)"))), fatSHFrame(Gtk::manage(new Gtk::Frame(M("TP_LOCALLAB_FATSHFRA")))), fatamountSH(Gtk::manage(new Adjuster(M("TP_LOCALLAB_FATAMOUNT"), 1., 100., 1., 1.))), - fatanchorSH(Gtk::manage(new Adjuster(M("TP_LOCALLAB_FATANCHOR"), 1., 100., 1., 50., Gtk::manage(new RTImage("circle-black-small.png")), Gtk::manage(new RTImage("circle-white-small.png"))))) + fatanchorSH(Gtk::manage(new Adjuster(M("TP_LOCALLAB_FATANCHOR"), 1., 100., 1., 50., Gtk::manage(new RTImage("circle-black-small.png")), Gtk::manage(new RTImage("circle-white-small.png"))))), + EvlocallabTePivot(ProcEventMapper::getInstance()->newEvent(AUTOEXP, "HISTORY_MSG_LOCALLAB_TE_PIVOT")) { set_orientation(Gtk::ORIENTATION_VERTICAL); @@ -4034,6 +4036,7 @@ LocallabShadow::LocallabShadow(): } detailSH->setAdjusterListener(this); + tePivot->setAdjusterListener(this); reparsh->setAdjusterListener(this); highlights->setAdjusterListener(this); @@ -4144,6 +4147,7 @@ LocallabShadow::LocallabShadow(): } pack_start(*detailSH); + pack_start(*tePivot); pack_start(*highlights); pack_start(*h_tonalwidth); pack_start(*shadows); @@ -4364,6 +4368,7 @@ void LocallabShadow::read(const rtengine::procparams::ProcParams* pp, const Para decays->setValue((double)spot.decays); detailSH->setValue((double)spot.detailSH); + tePivot->setValue(spot.tePivot); reparsh->setValue(spot.reparsh); highlights->setValue((double)spot.highlights); h_tonalwidth->setValue((double)spot.h_tonalwidth); @@ -4429,6 +4434,7 @@ void LocallabShadow::write(rtengine::procparams::ProcParams* pp, ParamsEdited* p } spot.detailSH = detailSH->getIntValue(); + spot.tePivot = tePivot->getValue(); spot.reparsh = reparsh->getValue(); spot.highlights = highlights->getIntValue(); spot.h_tonalwidth = h_tonalwidth->getIntValue(); @@ -4477,6 +4483,7 @@ void LocallabShadow::setDefaults(const rtengine::procparams::ProcParams* defPara } detailSH->setDefault((double)defSpot.detailSH); + tePivot->setDefault(defSpot.tePivot); reparsh->setDefault(defSpot.reparsh); highlights->setDefault((double)defSpot.highlights); h_tonalwidth->setDefault((double)defSpot.h_tonalwidth); @@ -4528,6 +4535,13 @@ void LocallabShadow::adjusterChanged(Adjuster* a, double newval) } } + if (a == tePivot) { + if (listener) { + listener->panelChanged(EvlocallabTePivot, + tePivot->getTextValue() + " (" + escapeHtmlChars(getSpotName()) + ")"); + } + } + if (a == reparsh) { if (listener) { listener->panelChanged(Evlocallabreparsh, @@ -5044,6 +5058,7 @@ void LocallabShadow::updateShadowGUI2() gamFrame->hide(); detailSH->hide(); + tePivot->hide(); highlights->show(); h_tonalwidth->show(); shadows->show(); @@ -5059,6 +5074,7 @@ void LocallabShadow::updateShadowGUI2() } detailSH->show(); + tePivot->show(); highlights->hide(); h_tonalwidth->hide(); shadows->hide(); diff --git a/rtgui/locallabtools.h b/rtgui/locallabtools.h index ced248ae1..1664d1839 100644 --- a/rtgui/locallabtools.h +++ b/rtgui/locallabtools.h @@ -451,6 +451,7 @@ private: Adjuster* const reparsh; const std::array multipliersh; Adjuster* const detailSH; + Adjuster* const tePivot; Adjuster* const highlights; Adjuster* const h_tonalwidth; Adjuster* const shadows; @@ -492,6 +493,8 @@ private: Adjuster* const fatamountSH; Adjuster* const fatanchorSH; + rtengine::ProcEvent EvlocallabTePivot; + sigc::connection shMethodConn, inversshConn, showmaskSHMethodConn, showmaskSHMethodConninv, enaSHMaskConn; public: diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 20450fa24..adf462d84 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -328,6 +328,11 @@ void ParamsEdited::set(bool v) sh.stonalwidth = v; sh.radius = v; sh.lab = v; + toneEqualizer.enabled = v; + toneEqualizer.bands.fill(v); + toneEqualizer.regularization = v; + toneEqualizer.show_colormap = v; + toneEqualizer.pivot = v; crop.enabled = v; crop.x = v; crop.y = v; @@ -1054,6 +1059,13 @@ 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; + for (size_t i = 0; i < toneEqualizer.bands.size(); ++i) { + toneEqualizer.bands[i] = toneEqualizer.bands[i] && p.toneEqualizer.bands[i] == other.toneEqualizer.bands[i]; + } + 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; @@ -1293,6 +1305,7 @@ void ParamsEdited::initFrom(const std::vector& locallab.spots.at(j).slomaskSH = locallab.spots.at(j).slomaskSH && pSpot.slomaskSH == otherSpot.slomaskSH; locallab.spots.at(j).lapmaskSH = locallab.spots.at(j).lapmaskSH && pSpot.lapmaskSH == otherSpot.lapmaskSH; locallab.spots.at(j).detailSH = locallab.spots.at(j).detailSH && pSpot.detailSH == otherSpot.detailSH; + locallab.spots.at(j).tePivot = locallab.spots.at(j).tePivot && pSpot.tePivot == otherSpot.tePivot; locallab.spots.at(j).reparsh = locallab.spots.at(j).reparsh && pSpot.reparsh == otherSpot.reparsh; locallab.spots.at(j).LmaskSHcurve = locallab.spots.at(j).LmaskSHcurve && pSpot.LmaskSHcurve == otherSpot.LmaskSHcurve; locallab.spots.at(j).fatamountSH = locallab.spots.at(j).fatamountSH && pSpot.fatamountSH == otherSpot.fatamountSH; @@ -3263,6 +3276,37 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.sh.lab = mods.sh.lab; } + if (toneEqualizer.enabled) { + toEdit.toneEqualizer.enabled = mods.toneEqualizer.enabled; + } + + for (size_t i = 0; i < toneEqualizer.bands.size(); ++i) { + if (toneEqualizer.bands[i]) { + toEdit.toneEqualizer.bands[i] = + dontforceSet && options.baBehav[ADDSET_TONE_EQUALIZER_BANDS] + ? toEdit.toneEqualizer.bands[i] + mods.toneEqualizer.bands[i] + : mods.toneEqualizer.bands[i]; + } + } + + if (toneEqualizer.regularization) { + toEdit.toneEqualizer.regularization = + dontforceSet && options.baBehav[ADDSET_TONE_EQUALIZER_REGULARIZATION] + ? toEdit.toneEqualizer.regularization + mods.toneEqualizer.regularization + : mods.toneEqualizer.regularization; + } + + if (toneEqualizer.show_colormap) { + toEdit.toneEqualizer.show_colormap = mods.toneEqualizer.show_colormap; + } + + if (toneEqualizer.pivot) { + toEdit.toneEqualizer.pivot = + dontforceSet && options.baBehav[ADDSET_TONE_EQUALIZER_PIVOT] + ? toEdit.toneEqualizer.pivot + mods.toneEqualizer.pivot + : mods.toneEqualizer.pivot; + } + if (crop.enabled) { toEdit.crop.enabled = mods.crop.enabled; } @@ -4205,6 +4249,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.locallab.spots.at(i).detailSH = mods.locallab.spots.at(i).detailSH; } + if (locallab.spots.at(i).tePivot) { + toEdit.locallab.spots.at(i).tePivot = mods.locallab.spots.at(i).tePivot; + } + if (locallab.spots.at(i).reparsh) { toEdit.locallab.spots.at(i).reparsh = mods.locallab.spots.at(i).reparsh; } @@ -7654,6 +7702,7 @@ LocallabParamsEdited::LocallabSpotEdited::LocallabSpotEdited(bool v) : slomaskSH(v), lapmaskSH(v), detailSH(v), + tePivot(v), reparsh(v), LmaskSHcurve(v), fatamountSH(v), @@ -8349,6 +8398,7 @@ void LocallabParamsEdited::LocallabSpotEdited::set(bool v) slomaskSH = v; lapmaskSH = v; detailSH = v; + tePivot = v; reparsh = v; LmaskSHcurve = v; fatamountSH = v; diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index d85bec701..3c8a70b74 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -18,6 +18,7 @@ */ #pragma once +#include #include namespace rtengine @@ -370,6 +371,14 @@ struct SHParamsEdited { bool lab; }; +struct ToneEqualizerParamsEdited { + bool enabled; + std::array bands; + bool regularization; + bool show_colormap; + bool pivot; +}; + struct CropParamsEdited { bool enabled; bool x; @@ -591,6 +600,7 @@ public: bool slomaskSH; bool lapmaskSH; bool detailSH; + bool tePivot; bool reparsh; bool LmaskSHcurve; bool fatamountSH; @@ -1558,6 +1568,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 81847adc0..3f03d81f6 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"))); @@ -332,6 +333,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); @@ -498,6 +500,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)); @@ -706,6 +709,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); @@ -717,6 +721,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 ()); @@ -894,6 +899,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 dcf44bb72..1dee55a6e 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; @@ -226,6 +227,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/preferences.cc b/rtgui/preferences.cc index 1ae01da2d..c9737704e 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -232,6 +232,12 @@ Gtk::Widget* Preferences::getBatchProcPanel() appendBehavList(mi, M("TP_SHADOWSHLIGHTS_HIGHLIGHTS"), ADDSET_SH_HIGHLIGHTS, false); appendBehavList(mi, M("TP_SHADOWSHLIGHTS_SHADOWS"), ADDSET_SH_SHADOWS, false); + mi = behModel->append(); + mi->set_value(behavColumns.label, M("TP_TONE_EQUALIZER_LABEL")); + appendBehavList(mi, M("TP_TONE_EQUALIZER_BANDS"), ADDSET_TONE_EQUALIZER_BANDS, false); + appendBehavList(mi, M("TP_TONE_EQUALIZER_PIVOT"), ADDSET_TONE_EQUALIZER_PIVOT, false); + appendBehavList(mi, M("TP_TONE_EQUALIZER_DETAIL"), ADDSET_TONE_EQUALIZER_REGULARIZATION, false); + mi = behModel->append(); mi->set_value(behavColumns.label, M("TP_LABCURVE_LABEL")); appendBehavList(mi, M("TP_LABCURVE_BRIGHTNESS"), ADDSET_LC_BRIGHTNESS, false); diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index dd7995feb..96a8bf849 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -463,6 +463,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..d524bdc05 --- /dev/null +++ b/rtgui/toneequalizer.cc @@ -0,0 +1,237 @@ +/* + * 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; + +const Glib::ustring ToneEqualizer::TOOL_NAME = "toneequalizer"; + +ToneEqualizer::ToneEqualizer(): FoldableToolPanel(this, TOOL_NAME, M("TP_TONE_EQUALIZER_LABEL"), false, true) +{ + auto m = ProcEventMapper::getInstance(); + EvEnabled = m->newEvent(AUTOEXP, "HISTORY_MSG_TONE_EQUALIZER_ENABLED"); + EvBands = m->newEvent(AUTOEXP, "HISTORY_MSG_TONE_EQUALIZER_BANDS"); + EvRegularization = m->newEvent(AUTOEXP, "HISTORY_MSG_TONE_EQUALIZER_REGULARIZATION"); + EvColormap = m->newEvent(AUTOEXP, "HISTORY_MSG_TONE_EQUALIZER_SHOW_COLOR_MAP"); + EvPivot = m->newEvent(AUTOEXP, "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, 0)); + regularization->setAdjusterListener(this); + pack_start(*regularization); + + show_colormap = Gtk::manage(new CheckBox(M("TP_TONE_EQUALIZER_SHOW_COLOR_MAP"), multiImage)); + pack_start(*show_colormap); + show_colormap->setCheckBoxListener(this); + + show_all_children (); +} + + +void ToneEqualizer::read(const ProcParams *pp, const ParamsEdited* pedited) +{ + disableListener(); + + if (pedited) { + set_inconsistent(multiImage && !pedited->toneEqualizer.enabled); + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->setEditedState(pedited->toneEqualizer.bands[i] ? Edited : UnEdited); + } + regularization->setEditedState(pedited->toneEqualizer.regularization ? Edited : UnEdited); + pivot->setEditedState(pedited->toneEqualizer.pivot ? Edited : UnEdited); + show_colormap->setEdited(pedited->toneEqualizer.show_colormap ? Edited : UnEdited); + } + + 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->setValue(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->getLastActive(); + pp->toneEqualizer.pivot = pivot->getValue(); + + if (pedited) { + auto &edited = pedited->toneEqualizer; + edited.enabled = !get_inconsistent(); + for (size_t i = 0; i < bands.size(); ++i) { + edited.bands[i] = bands[i]->getEditedState(); + } + edited.regularization = regularization->getEditedState(); + edited.pivot = pivot->getEditedState(); + edited.show_colormap = show_colormap->getEdited(); + } +} + + +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; + + if (pedited) { + auto &edited = pedited->toneEqualizer; + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->setDefaultEditedState(edited.bands[i] ? Edited : UnEdited); + } + regularization->setDefaultEditedState(edited.regularization ? Edited : UnEdited); + pivot->setDefaultEditedState(edited.pivot ? Edited : UnEdited); + } else { + for (auto band : bands) { + band->setDefaultEditedState(Irrelevant); + } + regularization->setDefaultEditedState(Irrelevant); + pivot->setDefaultEditedState(Irrelevant); + } +} + + +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::setBatchMode(bool batchMode) +{ + ToolPanel::setBatchMode(batchMode); + if (batchMode) { + for (auto band : bands) { + band->showEditedCB(); + } + regularization->showEditedCB(); + pivot->showEditedCB(); + } +} + + +void ToneEqualizer::setAdjusterBehavior(bool bands_add, bool regularization_add, bool pivot_add) +{ + for (auto band : bands) { + band->setAddMode(bands_add); + } + regularization->setAddMode(regularization_add); + pivot->setAddMode(pivot_add); +} + + +void ToneEqualizer::checkBoxToggled(CheckBox *c, CheckValue newval) +{ + if (c == show_colormap) { + colormapToggled(); + } +} + + +void ToneEqualizer::colormapToggled() +{ + for (size_t i = 0; i < bands.size(); ++i) { + bands[i]->showIcons(show_colormap->getLastActive()); + } + if (listener && getEnabled()) { + listener->panelChanged(EvColormap, show_colormap->getLastActive() ? 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..88a275799 --- /dev/null +++ b/rtgui/toneequalizer.h @@ -0,0 +1,62 @@ +/* -*- 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 "checkbox.h" +#include "toolpanel.h" + +class ToneEqualizer: public ToolParamBlock, public AdjusterListener, public FoldableToolPanel, public CheckBoxListener { +public: + static const Glib::ustring TOOL_NAME; + + 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 setBatchMode(bool batchMode) override; + void setAdjusterBehavior(bool bands_add, bool regularization_add, bool pivot_add); + void checkBoxToggled(CheckBox* c, CheckValue newval) override; + + void trimValues(rtengine::procparams::ProcParams *pp) override; + +private: + void colormapToggled(); + + std::array bands; + Adjuster *regularization; + Adjuster *pivot; + CheckBox *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/toollocationpref.cc b/rtgui/toollocationpref.cc index cf25dbc34..a2e2c9480 100644 --- a/rtgui/toollocationpref.cc +++ b/rtgui/toollocationpref.cc @@ -70,6 +70,8 @@ Glib::ustring getToolTitleKey(Tool tool) return "TP_EXPOSURE_LABEL"; case Tool::SHADOWS_HIGHLIGHTS: return "TP_SHADOWSHLIGHTS_LABEL"; + case Tool::TONE_EQUALIZER: + return "TP_TONE_EQUALIZER_LABEL"; case Tool::IMPULSE_DENOISE: return "TP_IMPULSEDENOISE_LABEL"; case Tool::DEFRINGE_TOOL: diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 92bf3d7a5..e9b0ce5cc 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -44,6 +44,9 @@ const std::vector EXPOSURE_PANEL_TOOLS = { { .id = Tool::SHADOWS_HIGHLIGHTS, }, + { + .id = Tool::TONE_EQUALIZER, + }, { .id = Tool::EPD, }, @@ -286,6 +289,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 ()); @@ -561,6 +565,8 @@ std::string ToolPanelCoordinator::getToolName(Tool tool) return ToneCurve::TOOL_NAME; case Tool::SHADOWS_HIGHLIGHTS: return ShadowsHighlights::TOOL_NAME; + case Tool::TONE_EQUALIZER: + return ToneEqualizer::TOOL_NAME; case Tool::IMPULSE_DENOISE: return ImpulseDenoise::TOOL_NAME; case Tool::DEFRINGE_TOOL: @@ -1907,6 +1913,8 @@ FoldableToolPanel *ToolPanelCoordinator::getFoldableToolPanel(Tool tool) const return toneCurve; case Tool::SHADOWS_HIGHLIGHTS: return shadowshighlights; + case Tool::TONE_EQUALIZER: + return toneEqualizer; case Tool::IMPULSE_DENOISE: return impulsedenoise; case Tool::DEFRINGE_TOOL: diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 7542343eb..95350c5e8 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -79,6 +79,7 @@ #include "softlight.h" #include "spot.h" #include "tonecurve.h" +#include "toneequalizer.h" #include "toolbar.h" #include "toolpanel.h" #include "vibrance.h" @@ -134,6 +135,7 @@ protected: Crop* crop; ToneCurve* toneCurve; ShadowsHighlights* shadowshighlights; + ToneEqualizer* toneEqualizer; LocalContrast *localContrast; Spot* spot; Defringe* defringe; @@ -245,6 +247,7 @@ public: enum class Tool { TONE_CURVE, SHADOWS_HIGHLIGHTS, + TONE_EQUALIZER, IMPULSE_DENOISE, DEFRINGE_TOOL, SPOT,