diff --git a/rtdata/languages/default b/rtdata/languages/default index 32bd3da42..16d264ff5 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1023,6 +1023,7 @@ HISTORY_MSG_782;Local - Blur Denoise Mask Wavelet levels HISTORY_MSG_783;Local - Color Mask Wavelet levels HISTORY_MSG_784;Local - Mask DeltaE HISTORY_MSG_785;Local - Mask Scope DeltaE +HISTORY_MSG_786;Local - SH method HISTORY_MSG_787;Local - Equalizer multiplier HISTORY_MSG_788;Local - Equalizer detail HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors diff --git a/rtengine/iplocallab.cc b/rtengine/iplocallab.cc index 6ad85e3f9..cc96d3586 100644 --- a/rtengine/iplocallab.cc +++ b/rtengine/iplocallab.cc @@ -308,6 +308,8 @@ struct local_params { float noisecf; float noisecc; float mulloc[6]; + int mullocsh[5]; + int detailsh; float threshol; float chromacb; float strengt; @@ -606,6 +608,12 @@ static void calcLocalParams(int sp, int oW, int oH, const LocallabParams& locall multi[y] = ((float) locallab.spots.at(sp).mult[y]); } + float multish[5]; + + for (int y = 0; y < 5; y++) { + multish[y] = ((float) locallab.spots.at(sp).multsh[y]); + } + float thresho = ((float)locallab.spots.at(sp).threshold); float chromcbdl = (float)locallab.spots.at(sp).chromacbdl; @@ -857,6 +865,11 @@ static void calcLocalParams(int sp, int oW, int oH, const LocallabParams& locall lp.mulloc[y] = CLIP04(multi[y]);//to prevent crash with old pp3 integer } + for (int y = 0; y < 5; y++) { + lp.mullocsh[y] = multish[y]; + } + + lp.detailsh = locallab.spots.at(sp).detailSH; lp.threshol = thresho; lp.chromacb = chromcbdl; lp.colorena = locallab.spots.at(sp).expcolor && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0; // Color & Light tool is deactivated if Exposure mask is visible or SHMask @@ -996,6 +1009,233 @@ static void calcTransition(const float lox, const float loy, const float ach, co } } +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 Adjustement 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 . +*/ + +{ + const int W = R.width(); + const int H = R.height(); + 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); + }; +// printf("mullocsh0=%i mullocsh2=%i\n", lp.mullocsh[0], lp.mullocsh[2]); + 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 = float(detail) / scale + 0.5f; + float epsilon2 = 0.01f + 0.002f * 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(std::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.f / scale; + epsilon2 = base_epsilon / float(6 - std::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 = max(log2(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; + } + } + printf("OK 18\n"); + +} void ImProcFunctions::ciecamloc_02float(int sp, LabImage* lab) @@ -7137,6 +7377,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o if (((radius > 1.5 * GAUSS_SKIP) || lp.stren > 0.1 || lp.blmet == 1 || lp.guidb > 1 || lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 3 || lp.showmaskblmet == 4) && lp.blurena) { blurz = true; } + const int GW = transformed->W; const int GH = transformed->H; @@ -8352,8 +8593,13 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o //shadow highlight + bool tonequ = false; - if (! lp.invsh && (lp.highlihs > 0.f || lp.shadowhs > 0.f || lp.showmaskSHmet == 2 || lp.enaSHMask || lp.showmaskSHmet == 3 || lp.showmaskSHmet == 4) && call < 3 && lp.hsena) { + if (lp.mullocsh[0] != 0 || lp.mullocsh[1] != 0 || lp.mullocsh[2] != 0 || lp.mullocsh[3] != 0 || lp.mullocsh[4] != 0) { + tonequ = true; + } + + if (! lp.invsh && (lp.highlihs > 0.f || lp.shadowhs > 0.f || tonequ || lp.showmaskSHmet == 2 || lp.enaSHMask || lp.showmaskSHmet == 3 || lp.showmaskSHmet == 4) && call < 3 && lp.hsena) { const int ystart = std::max(static_cast(lp.yc - lp.lyT) - cy, 0); const int yend = std::min(static_cast(lp.yc + lp.ly) - cy, original->H); const int xstart = std::max(static_cast(lp.xc - lp.lxL) - cx, 0); @@ -8470,7 +8716,52 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o } } - ImProcFunctions::shadowsHighlights(bufexpfin.get(), lp.hsena, 1, lp.highlihs, lp.shadowhs, lp.radiushs, sk, lp.hltonalhs, lp.shtonalhs); + if (params->locallab.spots.at(sp).shMethod == "std") { + ImProcFunctions::shadowsHighlights(bufexpfin.get(), lp.hsena, 1, lp.highlihs, lp.shadowhs, lp.radiushs, sk, lp.hltonalhs, lp.shtonalhs); + } + + if (params->locallab.spots.at(sp).shMethod == "tone") { + printf("OK 1 \n"); + array2D Rtemp; + Rtemp(bfw, bfh); + array2D Gtemp; + Gtemp(bfw, bfh); + array2D Btemp; + Btemp(bfw, bfh); + double scal = (double) (sk); + Imagefloat *tmpImage = nullptr; + tmpImage = new Imagefloat(bfw, bfh); + lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile); +#ifdef _OPENMP + #pragma omp parallel for schedule(dynamic,16) +#endif + + for (int y = 0; y < bfh ; y++) { + for (int x = 0; x < bfw; x++) { + Rtemp[y][x] = LIM01(tmpImage->r(y, x) / 65536.f); + Gtemp[y][x] = LIM01(tmpImage->g(y, x) / 65536.f); + Btemp[y][x] = LIM01(tmpImage->b(y, x) / 65536.f); + } + } + + tone_eq(Rtemp, Gtemp, Btemp, lp, params->icm.workingProfile, scal, multiThread); + +#ifdef _OPENMP + #pragma omp parallel for schedule(dynamic,16) +#endif + + for (int y = 0; y < bfh ; y++) { + for (int x = 0; x < bfw; x++) { + tmpImage->r(y, x) = 65536.f * Rtemp[y][x]; + tmpImage->g(y, x) = 65536.f * Gtemp[y][x]; + tmpImage->b(y, x) = 65536.f * Btemp[y][x]; + } + } + + rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile); + + delete tmpImage; + } #ifdef _OPENMP #pragma omp parallel for schedule(dynamic,16) @@ -8539,7 +8830,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o float pde = params->locallab.spots.at(sp).laplac; LocwavCurve dummy; bool lmasutilicolwav = false; - // bool delt = params->locallab.spots.at(sp).deltae; + // bool delt = params->locallab.spots.at(sp).deltae; bool delt = false; int sco = params->locallab.spots.at(sp).scopemask; @@ -10776,7 +11067,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o //inverse - else if (lp.invex && (lp.expcomp != 0.0 || lp.war != 0 || lp.laplacexp > 0.1f || params->locallab.spots.at(sp).fatamount > 1.f|| (exlocalcurve && localexutili) || lp.showmaskexpmet == 2 || lp.enaExpMask || lp.showmaskexpmet == 3 || lp.showmaskexpmet == 4 || lp.showmaskexpmet == 5) && lp.exposena) { + else if (lp.invex && (lp.expcomp != 0.0 || lp.war != 0 || lp.laplacexp > 0.1f || params->locallab.spots.at(sp).fatamount > 1.f || (exlocalcurve && localexutili) || lp.showmaskexpmet == 2 || lp.enaExpMask || lp.showmaskexpmet == 3 || lp.showmaskexpmet == 4 || lp.showmaskexpmet == 5) && lp.exposena) { float adjustr = 2.f; std::unique_ptr bufmaskblurexp; std::unique_ptr originalmaskexp; @@ -10829,7 +11120,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o float pde = params->locallab.spots.at(sp).laplac; LocwavCurve dummy; bool lmasutilicolwav = false; - // bool delt = params->locallab.spots.at(sp).deltae; + // bool delt = params->locallab.spots.at(sp).deltae; bool delt = false; int sco = params->locallab.spots.at(sp).scopemask; int shado = 0; @@ -11307,7 +11598,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o int level_hl = params->locallab.spots.at(sp).csthresholdcol.getTopLeft(); int level_br = params->locallab.spots.at(sp).csthresholdcol.getBottomRight(); int level_hr = params->locallab.spots.at(sp).csthresholdcol.getTopRight(); - // bool delt = params->locallab.spots.at(sp).deltae; + // bool delt = params->locallab.spots.at(sp).deltae; bool delt = false; int sco = params->locallab.spots.at(sp).scopemask; diff --git a/rtgui/locallab.cc b/rtgui/locallab.cc index 4f0812412..1cd13b8da 100644 --- a/rtgui/locallab.cc +++ b/rtgui/locallab.cc @@ -1177,11 +1177,11 @@ Locallab::Locallab(): mask2SHCurveEditorG->curveListComplete(); ToolParamBlock* const shadhighBox = Gtk::manage(new ToolParamBlock()); -// shadhighBox->pack_start(*shMethod); -// for (int i = 0; i < 5; i++) { -// shadhighBox->pack_start(*multipliersh[i]); -// } -// shadhighBox->pack_start(*detailSH); + shadhighBox->pack_start(*shMethod); + for (int i = 0; i < 5; i++) { + shadhighBox->pack_start(*multipliersh[i]); + } + shadhighBox->pack_start(*detailSH); shadhighBox->pack_start(*highlights); shadhighBox->pack_start(*h_tonalwidth); shadhighBox->pack_start(*shadows); @@ -4977,7 +4977,28 @@ void Locallab::shMethodChanged() disableListener(); if (shMethod->get_active_row_number() == 0) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->hide(); + } + detailSH->hide(); + highlights->show(); + h_tonalwidth->show(); + shadows->show(); + s_tonalwidth->show(); + sh_radius->show(); +// blurSHde->show(); } else if (shMethod->get_active_row_number() == 1) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->show(); + } + detailSH->show(); + highlights->hide(); + h_tonalwidth->hide(); + shadows->hide(); + s_tonalwidth->hide(); + sh_radius->hide(); +// blurSHde->hide(); + } enableListener(); @@ -5661,6 +5682,7 @@ void Locallab::inversshChanged() blurSHde->show(); showmaskSHMethod->hide(); showmaskSHMethodinv->show(); + shMethod->set_active(0); } else { // printf("Pas Inv SH\n"); @@ -9100,6 +9122,30 @@ void Locallab::updateSpecificGUIState() expmasksh->show(); showmaskSHMethodinv->show(); showmaskSHMethod->hide(); + shMethod->set_active(0); + + if (shMethod->get_active_row_number() == 0) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->hide(); + } + detailSH->hide(); + highlights->show(); + h_tonalwidth->show(); + shadows->show(); + s_tonalwidth->show(); + sh_radius->show(); + } else if (shMethod->get_active_row_number() == 1) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->hide(); + } + detailSH->hide(); + highlights->hide(); + h_tonalwidth->hide(); + shadows->hide(); + s_tonalwidth->hide(); + sh_radius->hide(); + + } } else { // printf("GUI NON inv SH\n"); @@ -9158,6 +9204,28 @@ void Locallab::updateSpecificGUIState() ctboxsoftmethod->show(); } + if (shMethod->get_active_row_number() == 0) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->hide(); + } + detailSH->hide(); + highlights->show(); + h_tonalwidth->show(); + shadows->show(); + s_tonalwidth->show(); + sh_radius->show(); + } else if (shMethod->get_active_row_number() == 1) { + for (int i = 0; i < 5; i++) { + multipliersh[i]->show(); + } + detailSH->show(); + highlights->hide(); + h_tonalwidth->hide(); + shadows->hide(); + s_tonalwidth->hide(); + sh_radius->hide(); + + } if (blMethod->get_active_row_number() == 0) { radius->show();