/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *
 *
 *  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 <http://www.gnu.org/licenses/>.
 *  2016 - 2024 Jacques Desmis <jdesmis@gmail.com>
 *  2016 - 2020 Ingo Weyrich <heckflosse@i-weyrich.de>

 */
#include <cmath>
#include <fftw3.h>

#include "improcfun.h"
#include "colortemp.h"
#include "curves.h"
#include "gauss.h"
#include "iccstore.h"
#include "imagefloat.h"
#include "labimage.h"
#include "color.h"
#include "rt_math.h"
#include "jaggedarray.h"
#include "rt_algo.h"
#include "settings.h"
#include "rtgui/options.h"
#include "utils.h"
#include "iccmatrices.h"
#ifdef _OPENMP
#include <omp.h>
#endif
#include "rtgui/widgets/basic/thresholdselector.h"
#include "imagesource.h"
#include "simde_helper.h"

#include "cplx_wavelet_dec.h"
#include "ciecam02.h"

//#define BENCHMARK
#include "StopWatch.h"
#include "guidedfilter.h"
#include "boxblur.h"
#include "rescale.h"



#pragma GCC diagnostic warning "-Wall"
#pragma GCC diagnostic warning "-Wextra"
#pragma GCC diagnostic warning "-Wdouble-promotion"



namespace
{

constexpr int limscope = 80;
constexpr int mSPsharp = 39; //minimum size Spot Sharp due to buildblendmask
constexpr int mSPsharpCS = 150; //minimum size Spot Sharp due to buildblendmask with capture sharpening to allow tiles 
constexpr int mSPwav = 32; //minimum size Spot Wavelet
constexpr int mDEN = 128; //minimum size Spot Denoise
constexpr int mSP = 5; //minimum size Spot
constexpr float MAXSCOPE = 1.25f;
constexpr float MINSCOPE = 0.025f;
constexpr int TS = 64; // Tile size
constexpr float epsilonw = 0.001f / (TS * TS); //tolerance
constexpr int offset = 25; // shift between tiles
constexpr double czlim = rtengine::RT_SQRT1_2;// 0.70710678118654752440;

float clamp(float x, float lo, float hi)
{
    return fmax(fmin(x, hi), lo);
}

// Michaelis-Menten equation
// Curiously we use the Michaelis-Menten equation, which is borrowed from biochemistry to describe enzyme kinetics
float mm_curve(double x, double S, double K_eff)
{
    // Ensure K_eff is not zero to prevent division by zero if x is also zero.
    // A very small K makes the curve rise very steeply.
    return (S * x) / (fmax( K_eff, 1e-6) +  x);
}


constexpr float clipLoc(float x)
{
    //return rtengine::LIM(x, 0.f, 32767.f);//remove leads to bad behavior
    return x;
}

constexpr float clipDE(float x)
{
    return rtengine::LIM(x, 0.3f, 1.f);
}

constexpr float clipR(float x)
{
    return rtengine::LIM(x, 0.f, 65535.f);//used when Laplacian Contrast attenuator
}


constexpr float clipC(float x)
{
    return rtengine::LIM(x, -100000.f, 100000.f);//increase LIM from 42000 to 1000000 to avoid clip and also imaginaries colors
}

constexpr float clipChro(float x)
{
    return rtengine::LIM(x, 0.f, 300.f);//increase LIM from 140 to 300 to avoid clip and also imaginaries colors
}

constexpr double clipazbz(double x)
{
    return rtengine::LIM(x, -0.5, 0.5);
}

constexpr double clipcz(double x)
{
    return rtengine::LIM(x, 0., czlim);
}


constexpr double clipjz05(double x)
{
    return rtengine::LIM(x, 0.0006, 1.0);
}

float softlig(float a, float b, float minc, float maxc)
{
    // as Photoshop
    if (2.f * b <= maxc - minc) {
        return a * (2.f * b + a * (maxc - 2.f * b));
    } else {
        return 2.f * a * (maxc - b) + std::sqrt(rtengine::LIM(a, 0.f, 2.f)) * (2.f * b - maxc);
    }
}

float softlig3(float a, float b)
{
    // as w3C
    if (2.f * b <= 1.f) {
        return a - (1.f - 2.f * b) * a * (1.f - a);
    } else {
        if (4.f * a <= 1.f) {
            return a + (2.f * b - 1.f) * (4.f * a * (4.f * a + 1.f) * (a - 1.f) + 7.f * a);
        } else {
            return a + (2.f * a - 1.f) * (std::sqrt(a) - a);
        }
    }
}

float softlig2(float a, float b)
{
    // illusions.hu
    return pow_F(b, pow_F(2.f, (2.f * (0.5f - a))));
}

constexpr float colburn(float a, float b)
{
    // w3C
    return b == 0.f ? 0.f : 1.f - rtengine::min(1.f, (1.f - a) / b);
}

constexpr float coldodge(float a, float b)
{
    // w3C
    return b == 1.f ? 1.f : rtengine::min(1.f, a / (1.f - b));
}

float overlay(float a, float b, float minc, float maxc)
{
    if (2.f * b <= maxc - minc) {
        return 2.f * b * a;
    } else {
        return maxc - 2.f * (1.f - a) * (maxc - b);
    }
}

constexpr float screen(float a, float b, float maxc)
{
    return 1.f - (1.f - a) * (maxc - b);
}

constexpr float exclusion(float a, float b)
{
    return a + b - 2.f * a * b;
}

void calcdif(float lmr, float &lmrc)
{
    //approximative change between gamma sRGB g=2.4 s=12.92 and gamma LAB g=3.0 s=9.03
    //useful to calculate action with dark and light area mask
    //differences in 3 parts linear...very small differences with real...
    float a0 = 7.6f / 11.6f;//11.6 sRGB  - 7.6 Lab...11.6 max difference
    float a01 = 62.f - 7.6f; //60 sRGB 62 Lab   60 max difference
    float a11 = 60.f - 11.6f;
    float a1 = a01 / a11;
    float b1 = 62.f - a1 * 60.f;
    float a2 = (100.f - 62.f) / (100.f - 60.f);
    float b2 = 100.f - a2 * 100.f;

    if (lmr < 11.6f) {
        lmrc = a0 * lmr;
    } else if (lmr < 60.f) {
        lmrc = a1 * lmr + b1;
    } else {
        lmrc = a2 * lmr + b2;
    }
}

void calcGammaLut(double gamma, double ts, LUTf &gammaLut)
{
    double pwr = 1.0 / gamma;
    double gamm = gamma;
    const double gamm2 = gamma;
    rtengine::GammaValues g_a;

    if (gamm2 < 1.0) {
        std::swap(pwr, gamm);
    }

    rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

    const double start = gamm2 < 1. ? g_a[2] : g_a[3];
    const double add = g_a[4];
    const double mul = 1.0 + g_a[4];

    if (gamm2 < 1.) {
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic, 1024)
#endif

        for (int i = 0; i < 65536; i++) {
            const double x = rtengine::Color::igammareti(i / 65535.0, gamm, start, ts, mul, add);
            gammaLut[i] = 0.5 * rtengine::CLIP(x * 65535.0);  // CLIP avoid in some case extra values
        }
    } else {
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic, 1024)
#endif

        for (int i = 0; i < 65536; i++) {
            const double x = rtengine::Color::gammareti(i / 65535.0, gamm, start, ts, mul, add);
            gammaLut[i] = 0.5 * rtengine::CLIP(x * 65535.0);  // CLIP avoid in some case extra values
        }
    }
}



float calcLocalFactor(const float lox, const float loy, const float lcx, const float dx, const float lcy, const float dy, const float ach, const float gradient)
{
    //ellipse x2/a2 + y2/b2=1
    //transition ellipsoidal
    const float kelip = dx / dy;
    const float belip = rtengine::max(0.0001f, std::sqrt((rtengine::SQR((lox - lcx) / kelip) + rtengine::SQR(loy - lcy)))); //determine position ellipse ==> a and b

    //gradient allows differentiation between transition x and y
    const float rapy = std::fabs((loy - lcy) / belip);
    const float aelip = belip * kelip;
    const float degrad = aelip / dx;
    const float gradreal = gradient * rapy + 1.f;
    const float ap = rtengine::RT_PI_F / (1.f - ach);
    const float bp = rtengine::RT_PI_F - ap;
    return pow(0.5f * (1.f + xcosf(degrad * ap + bp)), rtengine::SQR(gradreal)); // trigo cos transition
}

float calcLocalFactorrect(const float lox, const float loy, const float lcx, const float dx, const float lcy, const float dy, const float ach, const float gradient)
{
    constexpr float eps = 0.0001f;
    const float krap = std::fabs(dx / dy);
    const float kx = lox - lcx;
    const float ky = loy - lcy;

    float ref;

    //gradient allows differentiation between transition x and y
    if (std::fabs(kx / (ky + eps)) < krap) {
        ref = std::sqrt(rtengine::SQR(dy) * (1.f + rtengine::SQR(kx / (ky + eps))));
    } else {
        ref = std::sqrt(rtengine::SQR(dx) * (1.f + rtengine::SQR(ky / (kx + eps))));
    }

    const float rad = rtengine::max(eps, std::sqrt(rtengine::SQR(kx) + rtengine::SQR(ky)));
    const float rapy = std::fabs((loy - lcy) / rad);
    const float gradreal = gradient * rapy + 1.f;

    const float coef = rad / ref;
    const float fact = (coef - 1.f) / (ach - 1.f);
    return pow(fact, rtengine::SQR(gradreal));
}

float calcreducdE(float dE, float maxdE, float mindE, float maxdElim, float mindElim, float iterat, int limscope, int scope)
{
    if (scope > limscope) {//80 arbitrary value, if we change we must change limscope
        if (dE > maxdElim) {
            return 0.f;
        } else if (dE > mindElim) {
            const float reducdElim = std::pow((dE - maxdElim) / (mindElim - maxdElim), iterat);
            const float aalim = (1.f - reducdElim) / 20.f;
            const float bblim = 1.f - 100.f * aalim;
            return aalim * scope + bblim;
        } else {
            return 1.f;
        }
    } else {
        if (dE > maxdE) {
            return 0.f;
        } else if (dE > mindE) {
            return std::pow((dE - maxdE) / (mindE - maxdE), iterat);
        } else {
            return 1.f;
        }
    }
}



void deltaEforLaplace(float *dE, const float lap, int bfw, int bfh, rtengine::LabImage* bufexporig, const float hueref, const float chromaref, const float lumaref)
{

    const float refa = chromaref * std::cos(hueref);
    const float refb = chromaref * std::sin(hueref);
    const float refL = lumaref;
    float maxdE = 5.f + MAXSCOPE * lap;

    float maxC = std::sqrt((rtengine::SQR(refa - bufexporig->a[0][0]) + rtengine::SQR(refb - bufexporig->b[0][0])) + rtengine::SQR(refL - bufexporig->L[0][0])) / 327.68f;
#ifdef _OPENMP
    #pragma omp parallel for reduction(max:maxC)
#endif

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            const float val = std::sqrt((rtengine::SQR(refa - bufexporig->a[y][x]) + rtengine::SQR(refb - bufexporig->b[y][x])) + rtengine::SQR(refL - bufexporig->L[y][x])) / 327.68f;
            dE[y * bfw + x] = val;
            maxC = rtengine::max(maxC, val);
        }
    }

    if (maxdE > maxC) {
        maxdE = maxC - 1.f;
    }

    const float ade = 1.f / (maxdE - maxC);
    //  const float bde = -ade * maxC;

#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16)
#endif

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            //  dE[y * bfw + x] = dE[y * bfw + x] >= maxdE ? ade * dE[y * bfw + x] + bde : 1.f;
            dE[y * bfw + x] = dE[y * bfw + x] >= maxdE ? ade * (dE[y * bfw + x] - maxC) : 1.f;

        }
    }
}

float calclight(float lum, const LUTf &lightCurveloc)
{
    return clipLoc(lightCurveloc[lum]);
}

float calclightinv(float lum, float koef, const LUTf &lightCurveloc)
{
    return koef != -100.f ? clipLoc(lightCurveloc[lum]) : 0.f;
}

float balancedeltaE(float kL)
{
    constexpr float mincurs = 0.05f; //0.3f; // minimum slider balan_
    constexpr float maxcurs = 2.5f; //1.7f; // maximum slider balan_
    constexpr float maxkab = 1.475f; //1.35; // 0.5 * (3 - 0.3)
    constexpr float minkab = 0.25f; //0.65; // 0.5 * (3 - 1.7)
    constexpr float abal = (maxkab - minkab) / (mincurs - maxcurs);
    constexpr float bbal = maxkab - mincurs * abal;
    return abal * kL + bbal;
}

void SobelCannyLuma(float **sobelL, float **luma, int bfw, int bfh, float radius)
{
    // base of the process to detect shape in complement of deltaE
    // use for calculate Spot reference
    // and for structure of the shape
    // actually , as the program don't use these function, I just create a simple "Canny" near of Sobel. This can be completed after with teta, etc.
    array2D<float> tmL(bfw, bfh);

    //inspired from Chen Guanghua Zhang Xiaolong
    //Sobel Horizontal
    constexpr float GX[3][3] = {
        {1.f, 0.f, -1.f},
        {2.f, 0.f, -2.f},
        {1.f, 0.f, -1.f}
    };

    //Sobel Vertical
    constexpr float GY[3][3] = {
        {1.f, 2.f, 1.f},
        {0.f, 0.f, 0.f},
        {-1.f, -2.f, -1.f}
    };

    if (radius > 0.f) {
        gaussianBlur(luma, tmL, bfw, bfh, rtengine::max(radius / 2.f, 0.5f));
    } else {
        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw ; x++) {
                tmL[y][x] = luma[y][x];
            }
        }
    }

    for (int x = 0; x < bfw; x++) {
        sobelL[0][x] = 0.f;
    }

    for (int y = 1; y < bfh - 1; y++) {
        sobelL[y][0] = 0.f;

        for (int x = 1; x < bfw - 1; x++) {
            float sumXL = 0.f;
            float sumYL = 0.f;

            for (int i = -1; i < 2; i += 2) {
                for (int j = -1; j < 2; j += 1) {
                    sumXL += GX[j + 1][i + 1] * tmL[y + i][x + j];
                    sumYL += GY[j + 1][i + 1] * tmL[y + i][x + j];
                }
            }

            //Edge strength
            //we can add if need teta = atan2 (sumYr, sumXr)
            sobelL[y][x] = rtengine::min(std::sqrt(rtengine::SQR(sumXL) + rtengine::SQR(sumYL)), 32767.f);
        }

        sobelL[y][bfw - 1] = 0.f;
    }

    for (int x = 0; x < bfw; x++) {
        sobelL[bfh - 1][x] = 0.f;
    }
}

float igammalog(float x, float p, float s, float g2, float g4)
{
    return x <= g2 ? x / s : pow_F((x + g4) / (1.f + g4), p);//continuous
}

#if defined(__SSE2__) || defined(RT_SIMDE)
vfloat igammalog(vfloat x, vfloat p, vfloat s, vfloat g2, vfloat g4)
{
    //  return x <= g2 ? x / s : pow_F((x + g4) / (1.f + g4), p);//continuous
    return vself(vmaskf_le(x, g2), x / s, pow_F((x + g4) / (F2V(1.f) + g4), p));
}
#endif

float gammalog(float x, float p, float s, float g3, float g4)
{
    return x <= g3 ? x * s : (1.f + g4) * xexpf(xlogf(x) / p) - g4;//used by Nlmeans
}

#if defined(__SSE2__) || defined(RT_SIMDE)
vfloat gammalog(vfloat x, vfloat p, vfloat s, vfloat g3, vfloat g4)
{
    //  return x <= g3 ? x * s : (1.f + g4) * xexpf(xlogf(x) / p) - g4;//continuous
    return vself(vmaskf_le(x, g3), x * s, (F2V(1.f) + g4) * xexpf(xlogf(x) / p) - g4);//improve by Ingo - used by Nlmeans

}
#endif
}

namespace rtengine

{
extern MyMutex *fftwMutex;

using namespace procparams;

struct local_params {
    float yc, xc;
    float ycent, xcent;
    float lx, ly;
    float lxL, lyT;
    float transweak;
    float transgrad;
    float iterat;
    float balance;
    float balanceh;
    int colorde;
    float cir;
    bool recur;
    float thr;
    float stru;
    int chro, cont, sens, sensh, senscb, sensbn, senstm, sensex, sensexclu, sensden, senslc, senssf, senshs, senscolor;
    float reparden;
    float repartm;
    float clarityml;
    float contresid;
    bool deltaem;
    float struco;
    float strengrid;
    float struexc;
    float blendmacol;
    float radmacol;
    float chromacol;
    float gammacol;
    float slomacol;
    float blendmalc;
    float radmalc;
    float chromalc;
    float radmaexp;
    float chromaexp;
    float gammaexp;
    float slomaexp;
    float strmaexp;
    float angmaexp;
    float str_mas;
    float ang_mas;
    float feather_mas;
    float strexp;
    float angexp;
    float featherexp;
    float strSH;
    float angSH;
    float featherSH;
    float strcol;
    float strcolab;
    float strcolh;
    float angcol;
    float feathcol;
    float strvib;
    float strvibab;
    float strvibh;
    float angvib;
    float feathervib;
    float angwav;
    float featherwav;
    float strwav;
    float blendmaL;
    float radmaL;
    float chromaL;

    float strengthw;
    float radiusw;
    float detailw;
    float gradw;
    float tloww;
    float thigw;
    float edgw;
    float basew;

    float anglog;
    float featherlog;
    float strlog;
    float softradiusexp;
    float softradiuscol;
    float softradiuscb;
    float softradiusret;
    float softradiustm;
    float blendmaexp;
    float radmaSH;
    float blendmaSH;
    float chromaSH;
    float gammaSH;
    float slomaSH;
    float radmavib;
    float blendmavib;
    float chromavib;
    float gammavib;
    float slomavib;
    float radmacb;
    float blendmacb;
    float chromacbm;
    float gammacb;
    float slomacb;
    float radmatm;
    float blendmatm;
    float chromatm;
    float gammatm;
    float slomatm;

    float radmabl;
    float blendmabl;
    float chromabl;
    float gammabl;
    float slomabl;

    float struexp;
    float blurexp;
    float blurcol;
    float blurcolmask;
    float contcolmask;
    float blurSH;
    float ligh;
    float gamc;
    float gamlc;
    float gamex;
    float lowA, lowB, highA, highB;
    float lowBmerg, highBmerg, lowAmerg, highAmerg;
    int shamo, shdamp, shiter, senssha, sensv;
    float neig;
    float strng;
    float lap;
    float lcamount;
    double shrad;
    double shblurr;
    double rad;
    double stren;
    int it;
    int guidb;
    float strbl;
    float epsb;
    float trans;
    float feath;
    int dehaze;
    int dehazeSaturation;
    int depth;
    bool inv;
    bool invex;
    bool invsh;
    bool curvact;
    bool invrad;
    bool invret;
    bool equret;
    bool equtm;
    bool invshar;
    bool actsp;
    bool ftwlc;
    bool ftwreti;
    float str;
    int qualmet;
    int qualcurvemet;
    int gridmet;
    bool prevdE;
    int showmaskcolmet;
    int showmaskcolmetinv;
    int showmaskexpmet;
    int showmaskexpmetinv;
    int showmaskSHmet;
    int showmaskSHmetinv;
    int showmaskvibmet;
    int showmasklcmet;
    int showmasksharmet;
    int showmaskcbmet;
    int showmaskretimet;
    int showmasksoftmet;
    int showmasktmmet;
    int showmaskblmet;
    int showmasklogmet;
    int showmask_met;
    int showmaskciemet;
    bool processwa;
    bool fftbl;
    float laplacexp;
    float balanexp;
    float linear;
    int fullim;
    int expmet;
    int softmet;
    int blurmet;
    int blmet;
    bool invmaskd;
    bool invmask;
    int smasktyp;
    int chromet;
    int quamet;
    int shmeth;
    int medmet;
    int locmet;
    float noiself;
    float noiself0;
    float noiself2;
    float noiseldetail;
    int detailthr;
    float recothr;
    float lowthr;
    float higthr;
    float recothrd;
    float lowthrd;
    float midthrd;
    float midthrdch;
    float higthrd;
    float decayd;
    float recothrc;
    float lowthrc;
    float higthrc;
    float decayc;
    float recothre;
    float lowthre;
    float higthre;
    float decaye;
    float recothrv;
    float lowthrv;
    float higthrv;
    float decayv;
    float recothrcb;
    float lowthrcb;
    float higthrcb;
    float decaycb;
    float recothrt;
    float lowthrt;
    float higthrt;
    float decayt;
    float recothrw;
    float lowthrw;
    float higthrw;
    float decayw;
    float recothrr;
    float lowthrr;
    float higthrr;
    float decayr;
    float recothrs;
    float lowthrs;
    float higthrs;
    float decays;
    float recothrl;
    float lowthrl;
    float higthrl;
    float decayl;
    float recothrcie;
    float lowthrcie;
    float higthrcie;
    float decaycie;
    float blurciemask;
    float contciemask;
    bool islogcie; 
    bool issmoothcie; 
    bool issmoothghs;
    float issmoothmich;

    float maxdataghs;
    float ghshp;
    int noiselequal;
    float noisechrodetail;
    float bilat;
    int nlstr;
    int nldet;
    int nlpat;
    int nlrad;
    int nliter;
    float nlgam;
    float noisegam;
    float noiselc;
    float noiselc4;
    float noiselc5;
    float noiselc6;
    float noisecf;
    float noisecc;
    float mulloc[6];
    int mullocsh[6];
    int detailsh;
    int whitescie;
    int midtcie;
    int midtmet;
    double tePivot;
    float threshol;
    float chromacb;
    float strengt;
    float gamm;
    float esto;
    float scalt;
    float rewe;
    float amo;
    bool colorena;
    bool blurena;
    bool tonemapena;
    bool retiena;
    bool sharpena;
    bool lcena;
    bool sfena;
    bool cbdlena;
    bool denoiena;
    bool wavcurvedenoi;
    bool expvib;
    bool exposena;
    bool hsena;
    bool vibena;
    bool logena;
    bool islocal;
    bool maskena;
    bool cieena;
    bool cut_past;
    float past;
    float satur;
    int blac;
    int shcomp;
    int shadex;
    int hlcomp;
    int hlcompthr;
    float expcomp;
    float expchroma;
    int excmet;
    int mergemet;
    int mergecolMethod;
    float opacol;
    int war;
    float adjch;
    int shapmet;
    int edgwmet;
    int neiwmet;
    bool enaColorMask;
    bool fftColorMask;
    bool enaColorMaskinv;
    bool enaExpMask;
    bool enaExpMaskinv;
    bool enaSHMask;
    bool enaSHMaskinv;
    bool enavibMask;
    bool enalcMask;
    bool enasharMask;
    bool enacbMask;
    bool enaretiMask;
    bool enaretiMasktmap;
    bool enatmMask;
    bool enablMask;
    bool enaLMask;
    bool ena_Mask;
    bool enacieMask;
    int highlihs;
    int shadowhs;
    int radiushs;
    int hltonalhs;
    int shtonalhs;
    int scalereti;
    float sourcegray;
    float targetgray;
    float blackev;
    float whiteev;
    float sourcegraycie;
    float targetgraycie;
    float blackevjz;
    float whiteevjz;
    float detail;
    float detailcie;
    float strgradcie;
    float anggradcie;
    float feathercie;
    bool satcie;
    bool satlog;
    int sensilog;
    int sensicie;
    int sensimas;
    bool Autogray;
    bool autocompute;
    float baselog;
    bool wavgradl;
    bool edgwena;
    bool lip3;
    int daubLen;
    float sigmadr;
    float sigmabl;
    float sigmaed;
    float sigmalc;
    float offslc;
    float sigmalc2;
    float residsha;
    float residshathr;
    float residhi;
    float residhithr;
    float residgam;
    float residslop;
    bool avoidneg;
    bool blwh;
    bool fftma;
    float blurma;
    float contma;
    bool activspot;
    float thrlow;
    float thrhigh;
    bool usemask;
    float lnoiselow;
    float radmacie;
    float blendmacie;
    float chromacie;
    float denoichmask;
    float mLjz;
    float mCjz;
    float softrjz;
    bool fftcieMask;
    float comprlo;
    float comprlocie;
    int moka;
    int sursouci;
    int smoothciem;
    float smoothtrc;
    
    float denocontra;
    float denorati;
    float denomas;
    bool contrsho;
    bool denoAutocontr;
    bool enacontr;

};



static void calcLocalParams(int sp, int oW, int oH,  const LocallabParams& locallab, struct local_params& lp, bool prevDeltaE, int llColorMask, int llColorMaskinv, int llExpMask, int llExpMaskinv, int llSHMask, int llSHMaskinv, int llvibMask, int lllcMask, int llsharMask, int llcbMask, int llretiMask, int llsoftMask, int lltmMask, int llblMask, int lllogMask, int ll_Mask, int llcieMask, const LocwavCurve & locwavCurveden, bool locwavdenutili)
{
    int w = oW;
    int h = oH;
    float circr = locallab.spots.at(sp).circrad;
    bool recur = locallab.spots.at(sp).recurs;
    float streng = ((float)locallab.spots.at(sp).stren);
    float gam = ((float)locallab.spots.at(sp).gamma);
    float est = ((float)locallab.spots.at(sp).estop);
    float scal_tm = ((float)locallab.spots.at(sp).scaltm);
    float rewe = ((float)locallab.spots.at(sp).rewei);
    float amo = ((float)locallab.spots.at(sp).amount);
    float strlight = ((float)locallab.spots.at(sp).streng);
    float strucc = locallab.spots.at(sp).struc;
    float laplac = ((float)locallab.spots.at(sp).laplace);
    float thre = locallab.spots.at(sp).thresh;

//    if (thre > 8.f || thre < 0.f) {//to avoid artifacts if user does not clear cache with new settings. Can be suppressed after
//        thre = 2.f;
//    }
    thre = LIM(thre, 0.f, 15.0f);

    double local_x = locallab.spots.at(sp).loc.at(0) / 2000.0;
    double local_y = locallab.spots.at(sp).loc.at(2) / 2000.0;
    double local_xL = locallab.spots.at(sp).loc.at(1) / 2000.0;
    double local_yT = locallab.spots.at(sp).loc.at(3) / 2000.0;
    double local_center_x = locallab.spots.at(sp).centerX / 2000.0 + 0.5;
    double local_center_y = locallab.spots.at(sp).centerY / 2000.0 + 0.5;
    float iterati = (float) locallab.spots.at(sp).iter;
    float balanc = (float) locallab.spots.at(sp).balan;
    float balanch = (float) locallab.spots.at(sp).balanh;
    int colorde = (int) locallab.spots.at(sp).colorde;

//    if (iterati > 4.f || iterati < 0.2f) {//to avoid artifacts if user does not clear cache with new settings Can be suppressed after
//       iterati = 2.f;
//    }
    iterati = LIM(iterati, 0.2f, 10.0f);

    float neigh = float (locallab.spots.at(sp).neigh);
    float chromaPastel = float (locallab.spots.at(sp).pastels)   / 100.0f;
    float chromaSatur  = float (locallab.spots.at(sp).saturated) / 100.0f;
    int local_sensiv = locallab.spots.at(sp).sensiv;
    int local_sensiex = locallab.spots.at(sp).sensiex;

    if (locallab.spots.at(sp).qualityMethod == "enh") {
        lp.qualmet = 1;
    } else if (locallab.spots.at(sp).qualityMethod == "enhden") {
        lp.qualmet = 2;
    }

    if (locallab.spots.at(sp).qualitycurveMethod == "none") {
        lp.qualcurvemet = 0;
    } else if (locallab.spots.at(sp).qualitycurveMethod == "std") {
        lp.qualcurvemet = 1;
    }

    if (locallab.spots.at(sp).gridMethod == "one") {
        lp.gridmet = 0;
    } else if (locallab.spots.at(sp).gridMethod == "two") {
        lp.gridmet = 1;
    }


    if (locallab.spots.at(sp).midtciemet == "one") {
        lp.midtmet = 0;
    } else if (locallab.spots.at(sp).midtciemet == "two") {
        lp.midtmet = 1;
    } else if (locallab.spots.at(sp).midtciemet == "thr") {
        lp.midtmet = 2;
    }

    /*
        if (locallab.spots.at(sp).expMethod == "std") {
            lp.expmet = 0;
        } else if (locallab.spots.at(sp).expMethod == "pde") {
            lp.expmet = 1;
        }
    */
    lp.expmet = 1;

    if (locallab.spots.at(sp).localcontMethod == "loc") {
        lp.locmet = 0;
    } else if (locallab.spots.at(sp).localcontMethod == "wav") {
        lp.locmet = 1;
    }

    lp.laplacexp = locallab.spots.at(sp).laplacexp;
    lp.balanexp = locallab.spots.at(sp).balanexp;
    lp.linear = locallab.spots.at(sp).linear;

    if (locallab.spots.at(sp).smoothciemet == "none") {
        lp.smoothciem = 0;
    } else if (locallab.spots.at(sp).smoothciemet == "Ev") {
        lp.smoothciem = 1;
    } else if (locallab.spots.at(sp).smoothciemet == "gam") {
        lp.smoothciem = 2;
    } else if (locallab.spots.at(sp).smoothciemet == "gamnorol") {
        lp.smoothciem = 3;
    } else if (locallab.spots.at(sp).smoothciemet == "level") {
        lp.smoothciem = 4;
    } else if (locallab.spots.at(sp).smoothciemet == "sigm") {
        lp.smoothciem = 6;
    } else if (locallab.spots.at(sp).smoothciemet == "trc") {
        lp.smoothciem = 7;
    }
    lp.smoothtrc = locallab.spots.at(sp).smoothciethtrc;


    if (locallab.spots.at(sp).spotMethod == "norm") {
        lp.fullim = 0;
    } else if (locallab.spots.at(sp).spotMethod == "exc") {
        lp.fullim = 1;
    } else if (locallab.spots.at(sp).spotMethod == "full") {
        lp.fullim = 2;
    } else if (locallab.spots.at(sp).spotMethod == "main") {//new Global
        lp.fullim = 3;
    }

    lp.fftColorMask = locallab.spots.at(sp).fftColorMask;
    lp.prevdE = prevDeltaE;
    lp.showmaskcolmet = llColorMask;
    lp.showmaskcolmetinv = llColorMaskinv;
    lp.showmaskexpmet = llExpMask;
    lp.showmaskexpmetinv = llExpMaskinv;
    lp.showmaskSHmet = llSHMask;
    lp.showmaskSHmetinv = llSHMaskinv;
    lp.showmaskvibmet = llvibMask;
    lp.showmasklcmet = lllcMask;
    lp.showmasksharmet = llsharMask;
    lp.showmaskcbmet = llcbMask;
    lp.showmaskretimet = llretiMask;
    lp.showmasksoftmet = llsoftMask;

    lp.showmasktmmet = lltmMask;
    lp.showmaskblmet = llblMask;
    lp.showmasklogmet = lllogMask;
    lp.showmask_met = ll_Mask;
    lp.showmaskciemet = llcieMask;
    lp.processwa = locallab.spots.at(sp).processwav;
    lp.fftcieMask = locallab.spots.at(sp).fftcieMask;
    lp.islogcie = locallab.spots.at(sp).logcie && locallab.spots.at(sp).expprecam;
    lp.issmoothcie = locallab.spots.at(sp).smoothcie;
    lp.issmoothghs = locallab.spots.at(sp).ghs_smooth;
    lp.issmoothmich = locallab.spots.at(sp).mich_high;

    lp.maxdataghs = 0.f;
    lp.ghshp =  locallab.spots.at(sp).ghs_HP;
    lp.enaColorMask = locallab.spots.at(sp).enaColorMask && llsoftMask == 0 && llColorMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llExpMaskinv == 0 && lllcMask == 0 && llsharMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// Exposure mask is deactivated if Color & Light mask is visible
    lp.enaColorMaskinv = locallab.spots.at(sp).enaColorMask && llColorMaskinv == 0 && llSHMaskinv == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// Exposure mask is deactivated if Color & Light mask is visible
    lp.enaExpMask = locallab.spots.at(sp).enaExpMask && llExpMask == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llColorMaskinv == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// Exposure mask is deactivated if Color & Light mask is visible
    lp.enaExpMaskinv = locallab.spots.at(sp).enaExpMask && llExpMaskinv == 0 && llColorMask == 0 && llSHMaskinv == 0 && llColorMaskinv == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// Exposure mask is deactivated if Color & Light mask is visible
    lp.enaSHMask = locallab.spots.at(sp).enaSHMask && llSHMask == 0 && llColorMask == 0 && llColorMaskinv == 0 && llSHMaskinv == 0 && llExpMaskinv == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llExpMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0  && ll_Mask == 0 && llcieMask == 0;
    lp.enaSHMaskinv = locallab.spots.at(sp).enaSHMask && llColorMaskinv == 0 && llSHMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && lllcMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enacbMask = locallab.spots.at(sp).enacbMask && llColorMaskinv == 0 && llcbMask == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && lllcMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enaretiMask = locallab.spots.at(sp).enaretiMask && llColorMaskinv == 0 && lllcMask == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llsharMask == 0 && llsoftMask == 0 && llretiMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enatmMask = locallab.spots.at(sp).enatmMask && llColorMaskinv == 0 && lltmMask == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && lllcMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0  && ll_Mask == 0 && llcieMask == 0;
    lp.enablMask = locallab.spots.at(sp).enablMask && llColorMaskinv == 0 && llblMask == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && lllcMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enavibMask = locallab.spots.at(sp).enavibMask && llvibMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && lllcMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llSHMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enalcMask = locallab.spots.at(sp).enalcMask && lllcMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llcbMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enasharMask = lllcMask == 0 && llcbMask == 0 && llsharMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llsoftMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.ena_Mask = locallab.spots.at(sp).enamask && lllcMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llcbMask == 0 && llsoftMask == 0 && llsharMask == 0 && llColorMask == 0 && llExpMask == 0 && llSHMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && lllogMask == 0 && llvibMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.enaLMask = locallab.spots.at(sp).enaLMask && lllogMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llExpMask == 0 && llSHMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && ll_Mask == 0 && llcieMask == 0;// Exposure mask is deactivated if Color & Light mask is visible
    lp.enacieMask = locallab.spots.at(sp).enacieMask && llcieMask == 0 && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llSHMask == 0 && llsoftMask == 0 && lllcMask == 0 && llsharMask == 0 && llExpMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llblMask == 0 && llvibMask == 0 && lllogMask == 0  && ll_Mask == 0;


    lp.thrlow = locallab.spots.at(sp).levelthrlow;
    lp.thrhigh = locallab.spots.at(sp).levelthr;
    lp.usemask = locallab.spots.at(sp).usemask;
    lp.lnoiselow = locallab.spots.at(sp).lnoiselow;

    if (locallab.spots.at(sp).softMethod == "soft") {
        lp.softmet = 0;
    } else if (locallab.spots.at(sp).softMethod == "reti") {
        lp.softmet = 1;
    }

    if (locallab.spots.at(sp).blMethod == "blur") {
        lp.blmet = 0;
    } else if (locallab.spots.at(sp).blMethod == "med") {
        lp.blmet = 1;
    } else if (locallab.spots.at(sp).blMethod == "guid") {
        lp.blmet = 2;
    }

    if (locallab.spots.at(sp).chroMethod == "lum") {
        lp.chromet = 0;
    } else if (locallab.spots.at(sp).chroMethod == "chr") {
        lp.chromet = 1;
    } else if (locallab.spots.at(sp).chroMethod == "all") {
        lp.chromet = 2;
    }




    if (locallab.spots.at(sp).quamethod == "cons") {
        lp.quamet = 0;
    } else if (locallab.spots.at(sp).quamethod == "agre") {
        lp.quamet = 1;
    } else if (locallab.spots.at(sp).quamethod == "nlmean") {
        lp.quamet = 2;
    } else if (locallab.spots.at(sp).quamethod == "none") {
        lp.quamet = 3;
    }


    if (locallab.spots.at(sp).shMethod == "std") {
        lp.shmeth = 0;
    } else if (locallab.spots.at(sp).shMethod == "tone") {
        lp.shmeth = 1;
    } else if (locallab.spots.at(sp).shMethod == "ghs") {
        lp.shmeth = 2;
    } else if (locallab.spots.at(sp).shMethod == "micha") {
        lp.shmeth = 3;
    }


    if (locallab.spots.at(sp).medMethod == "none") {
        lp.medmet = -1;
    } else if (locallab.spots.at(sp).medMethod == "33") {
        lp.medmet = 0;
    } else if (locallab.spots.at(sp).medMethod == "55") {
        lp.medmet = 1;
    } else if (locallab.spots.at(sp).medMethod == "77") {
        lp.medmet = 2;
    } else if (locallab.spots.at(sp).medMethod == "99") {
        lp.medmet = 3;
    }

    /*
        if (locallab.spots.at(sp).blurMethod == "norm") {
            lp.blurmet = 0;
        } else if (locallab.spots.at(sp).blurMethod == "inv") {
            lp.blurmet = 1;
        }
    */
    if (locallab.spots.at(sp).invbl == false) {
        lp.blurmet = 0;
    } else if (locallab.spots.at(sp).invbl == true) {
        lp.blurmet = 1;
    }

    if (locallab.spots.at(sp).invmask == false) {
        lp.invmask = false;
    } else if (locallab.spots.at(sp).invmask == true) {
        lp.invmask = true;
    }

    if (locallab.spots.at(sp).invmaskd == false) {
        lp.invmaskd = false;
    } else if (locallab.spots.at(sp).invmaskd == true) {
        lp.invmaskd = true;
    }


    if (locallab.spots.at(sp).showmaskblMethodtyp == "blur") {
        lp.smasktyp = 0;
    } else if (locallab.spots.at(sp).showmaskblMethodtyp == "nois") {
        lp.smasktyp = 1;
    } else if (locallab.spots.at(sp).showmaskblMethodtyp == "all") {
        lp.smasktyp = 2;
    }


    if (locallab.spots.at(sp).spotMethod == "norm") {
        lp.excmet = 0;
    } else if (locallab.spots.at(sp).spotMethod == "exc") {
        lp.excmet = 1;
    } else if (locallab.spots.at(sp).spotMethod == "full") {
        lp.excmet = 2;
    } else if (locallab.spots.at(sp).spotMethod == "main") {
        lp.excmet = 3;
    }

    if (locallab.spots.at(sp).merMethod == "mone") {
        lp.mergemet = 0;
    } else if (locallab.spots.at(sp).merMethod == "mtwo") {
        lp.mergemet = 1;
    } else if (locallab.spots.at(sp).merMethod == "mthr") {
        lp.mergemet = 2;
    } else if (locallab.spots.at(sp).merMethod == "mfou") {
        lp.mergemet = 3;
    } else if (locallab.spots.at(sp).merMethod == "mfiv") {
        lp.mergemet = 4;
    }

    if (locallab.spots.at(sp).mergecolMethod == "one") {
        lp.mergecolMethod = 0;
    } else if (locallab.spots.at(sp).mergecolMethod == "two") {
        lp.mergecolMethod = 1;
    } else if (locallab.spots.at(sp).mergecolMethod == "thr") {
        lp.mergecolMethod = 2;
    } else if (locallab.spots.at(sp).mergecolMethod == "fou") {
        lp.mergecolMethod = 3;
    } else if (locallab.spots.at(sp).mergecolMethod == "fiv") {
        lp.mergecolMethod = 4;
    } else if (locallab.spots.at(sp).mergecolMethod == "six") {
        lp.mergecolMethod = 5;
    } else if (locallab.spots.at(sp).mergecolMethod == "sev") {
        lp.mergecolMethod = 6;
    } else if (locallab.spots.at(sp).mergecolMethod == "sev0") {
        lp.mergecolMethod = 7;
    } else if (locallab.spots.at(sp).mergecolMethod == "sev1") {
        lp.mergecolMethod = 8;
    } else if (locallab.spots.at(sp).mergecolMethod == "sev2") {
        lp.mergecolMethod = 9;
    } else if (locallab.spots.at(sp).mergecolMethod == "hei") {
        lp.mergecolMethod = 10;
    } else if (locallab.spots.at(sp).mergecolMethod == "nin") {
        lp.mergecolMethod = 11;
    } else if (locallab.spots.at(sp).mergecolMethod == "ten") {
        lp.mergecolMethod = 12;
    } else if (locallab.spots.at(sp).mergecolMethod == "ele") {
        lp.mergecolMethod = 13;
    } else if (locallab.spots.at(sp).mergecolMethod == "twe") {
        lp.mergecolMethod = 14;
    } else if (locallab.spots.at(sp).mergecolMethod == "thi") {
        lp.mergecolMethod = 15;
    } else if (locallab.spots.at(sp).mergecolMethod == "for") {
        lp.mergecolMethod = 16;
    } else if (locallab.spots.at(sp).mergecolMethod == "hue") {
        lp.mergecolMethod = 17;
    } else if (locallab.spots.at(sp).mergecolMethod == "sat") {
        lp.mergecolMethod = 18;
    } else if (locallab.spots.at(sp).mergecolMethod == "col") {
        lp.mergecolMethod = 19;
    } else if (locallab.spots.at(sp).mergecolMethod == "lum") {
        lp.mergecolMethod = 20;
    }

    if (locallab.spots.at(sp).localedgMethod == "fir") {
        lp.edgwmet = 0;
    } else if (locallab.spots.at(sp).localedgMethod == "sec") {
        lp.edgwmet = 1;
    } else if (locallab.spots.at(sp).localedgMethod == "thr") {
        lp.edgwmet = 2;
    }

    if (locallab.spots.at(sp).localneiMethod == "none") {
        lp.neiwmet = -1;
        lp.lip3 = false;
    } else if (locallab.spots.at(sp).localneiMethod == "low") {
        lp.neiwmet = 0;
        lp.lip3 = true;
    } else if (locallab.spots.at(sp).localneiMethod == "high") {
        lp.lip3 = true;
        lp.neiwmet = 1;
    }


    if (locallab.spots.at(sp).wavMethod == "D2") {
        lp.daubLen = 4;
    } else if (locallab.spots.at(sp).wavMethod == "D4") {
        lp.daubLen = 6;
    } else if (locallab.spots.at(sp).wavMethod == "D6") {
        lp.daubLen = 8;
    } else if (locallab.spots.at(sp).wavMethod == "D10") {
        lp.daubLen = 12;
    } else if (locallab.spots.at(sp).wavMethod == "D14") {
        lp.daubLen = 16;
    } else if (locallab.spots.at(sp).wavMethod == "D20") {
        lp.daubLen = 22;
    }


    lp.edgwena = locallab.spots.at(sp).wavedg;

    lp.opacol = 0.01 * locallab.spots.at(sp).opacol;

    if (locallab.spots.at(sp).shape == "ELI") {
        lp.shapmet = 0;
    } else { /*if (locallab.spots.at(sp).shape == "RECT")*/
        lp.shapmet = 1;
    }

    lp.denoiena = locallab.spots.at(sp).expblur;

    bool wavcurveden = false;
    float local_noiself = 0.f;
    float local_noiself0 = 0.f;
    float local_noiself2 = 0.f;
    float local_noiselc = 0.f;
    float lnoiselc4 = 0.f;
    float lnoiselc5 = 0.f;
    float lnoiselc6 = 0.f;

    if (locwavCurveden && locwavdenutili) {
        if (lp.denoiena) {
            for (int i = 0; i < 500; i++) {
                if (locwavCurveden[i] != 0.f) {
                    wavcurveden = true;
                }
            }
        }
    }

    if (wavcurveden) {
        if (lp.denoiena) {
            local_noiself0 = 250.f * locwavCurveden[0];
            local_noiself = 250.f * locwavCurveden[83];
            local_noiself2 = 250.f * locwavCurveden[166];
            local_noiselc = 200.f * locwavCurveden[250];
            lnoiselc4 = 250.f * locwavCurveden[333];
            lnoiselc5 = 250.f * locwavCurveden[416];
            lnoiselc6 = 250.f * locwavCurveden[500];
        }
    }

    lp.wavcurvedenoi = wavcurveden;
    float local_noiseldetail = (float)locallab.spots.at(sp).noiselumdetail;
    int local_noiselequal = locallab.spots.at(sp).noiselequal;
    float local_noisechrodetail = (float)locallab.spots.at(sp).noisechrodetail;
    int local_sensiden = locallab.spots.at(sp).sensiden;
    float local_reparden = locallab.spots.at(sp).reparden;
    float local_repartm = locallab.spots.at(sp).repartm;
    float local_detailthr = (float)locallab.spots.at(sp).detailthr;
    float local_recothr = (float)locallab.spots.at(sp).recothres;
    float local_lowthr = (float)locallab.spots.at(sp).lowthres;
    float local_higthr = (float)locallab.spots.at(sp).higthres;
    float local_recothrd = (float)locallab.spots.at(sp).recothresd;
    float local_lowthrd = (float)locallab.spots.at(sp).lowthresd;
    float local_midthrd = (float)locallab.spots.at(sp).midthresd;
    float local_midthrdch = (float)locallab.spots.at(sp).midthresdch;
    float local_higthrd = (float)locallab.spots.at(sp).higthresd;
    float local_decayd = (float)locallab.spots.at(sp).decayd;
    float local_recothrc = (float)locallab.spots.at(sp).recothresc;
    float local_lowthrc = (float)locallab.spots.at(sp).lowthresc;
    float local_higthrc = (float)locallab.spots.at(sp).higthresc;
    float local_decayc = (float)locallab.spots.at(sp).decayc;

    float local_recothre = (float)locallab.spots.at(sp).recothrese;
    float local_lowthre = (float)locallab.spots.at(sp).lowthrese;
    float local_higthre = (float)locallab.spots.at(sp).higthrese;
    float local_decaye = (float)locallab.spots.at(sp).decaye;

    float local_recothrv = (float)locallab.spots.at(sp).recothresv;
    float local_lowthrv = (float)locallab.spots.at(sp).lowthresv;
    float local_higthrv = (float)locallab.spots.at(sp).higthresv;
    float local_decayv = (float)locallab.spots.at(sp).decayv;

    float local_recothrcb = (float)locallab.spots.at(sp).recothrescb;
    float local_lowthrcb = (float)locallab.spots.at(sp).lowthrescb;
    float local_higthrcb = (float)locallab.spots.at(sp).higthrescb;
    float local_decaycb = (float)locallab.spots.at(sp).decaycb;

    float local_recothrr = (float)locallab.spots.at(sp).recothresr;
    float local_lowthrr = (float)locallab.spots.at(sp).lowthresr;
    float local_higthrr = (float)locallab.spots.at(sp).higthresr;
    float local_decayr = (float)locallab.spots.at(sp).decayr;

    float local_recothrt = (float)locallab.spots.at(sp).recothrest;
    float local_lowthrt = (float)locallab.spots.at(sp).lowthrest;
    float local_higthrt = (float)locallab.spots.at(sp).higthrest;
    float local_decayt = (float)locallab.spots.at(sp).decayt;

    float local_recothrw = (float)locallab.spots.at(sp).recothresw;
    float local_lowthrw = (float)locallab.spots.at(sp).lowthresw;
    float local_higthrw = (float)locallab.spots.at(sp).higthresw;
    float local_decayw = (float)locallab.spots.at(sp).decayw;

    float local_recothrs = (float)locallab.spots.at(sp).recothress;
    float local_lowthrs = (float)locallab.spots.at(sp).lowthress;
    float local_higthrs = (float)locallab.spots.at(sp).higthress;
    float local_decays = (float)locallab.spots.at(sp).decays;

    float local_recothrcie = (float)locallab.spots.at(sp).recothrescie;
    float local_lowthrcie = (float)locallab.spots.at(sp).lowthrescie;
    float local_higthrcie = (float)locallab.spots.at(sp).higthrescie;
    float local_decaycie = (float)locallab.spots.at(sp).decaycie;

    float local_recothrl = (float)locallab.spots.at(sp).recothresl;
    float local_lowthrl = (float)locallab.spots.at(sp).lowthresl;
    float local_higthrl = (float)locallab.spots.at(sp).higthresl;
    float local_decayl = (float)locallab.spots.at(sp).decayl;

    float local_noisecf = ((float)locallab.spots.at(sp).noisechrof) / 10.f;
    float local_noisecc = ((float)locallab.spots.at(sp).noisechroc) / 10.f;
    float multi[6];

    for (int y = 0; y < 6; y++) {
        multi[y] = ((float) locallab.spots.at(sp).mult[y]);
    }

    float multish[6];

    for (int y = 0; y < 6; 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;

    int local_chroma = locallab.spots.at(sp).chroma;
    int local_sensi = locallab.spots.at(sp).sensi;
    int local_sensibn = locallab.spots.at(sp).sensibn;
    int local_sensitm = locallab.spots.at(sp).sensitm;
    int local_sensiexclu = locallab.spots.at(sp).sensiexclu;
    float structexclude = (float) locallab.spots.at(sp).structexclu;
    int local_sensilc = locallab.spots.at(sp).sensilc;
    int local_warm = locallab.spots.at(sp).warm;
    int local_sensih = locallab.spots.at(sp).sensih;
    int local_dehaze = locallab.spots.at(sp).dehaz;
    int local_depth = locallab.spots.at(sp).depth;
    int local_dehazeSaturation = locallab.spots.at(sp).dehazeSaturation;
    int local_sensicb = locallab.spots.at(sp).sensicb;
    float local_clarityml = (float) locallab.spots.at(sp).clarityml;
    float local_contresid = (float) locallab.spots.at(sp).contresid;
    int local_contrast = locallab.spots.at(sp).contrast;
    float local_lightness = (float) locallab.spots.at(sp).lightness;
    float labgridALowloc = locallab.spots.at(sp).labgridALow;
    float labgridBLowloc = locallab.spots.at(sp).labgridBLow;
    float labgridBHighloc = locallab.spots.at(sp).labgridBHigh;
    float labgridAHighloc = locallab.spots.at(sp).labgridAHigh;
    float strengthgrid = (float) locallab.spots.at(sp).strengthgrid;
    float labgridBLowlocmerg = locallab.spots.at(sp).labgridBLowmerg;
    float labgridBHighlocmerg = locallab.spots.at(sp).labgridBHighmerg;
    float labgridALowlocmerg = locallab.spots.at(sp).labgridALowmerg;
    float labgridAHighlocmerg = locallab.spots.at(sp).labgridAHighmerg;
    float local_gamlc = (float) locallab.spots.at(sp).gamlc;
    float local_gamc = (float) locallab.spots.at(sp).gamc;
    float local_gamex = (float) locallab.spots.at(sp).gamex;

    float blendmasklc = ((float) locallab.spots.at(sp).blendmasklc) / 100.f ;
    float radmasklc = ((float) locallab.spots.at(sp).radmasklc);
    float chromasklc = ((float) locallab.spots.at(sp).chromasklc);
    float structcolor = (float) locallab.spots.at(sp).structcol;
    float blendmaskcolor = ((float) locallab.spots.at(sp).blendmaskcol) / 100.f ;
    float radmaskcolor = ((float) locallab.spots.at(sp).radmaskcol);
    float chromaskcolor = ((float) locallab.spots.at(sp).chromaskcol);
    float gammaskcolor = ((float) locallab.spots.at(sp).gammaskcol);
    float slomaskcolor = ((float) locallab.spots.at(sp).slomaskcol);
    float blendmaskexpo = ((float) locallab.spots.at(sp).blendmaskexp) / 100.f ;
    float radmaskexpo = ((float) locallab.spots.at(sp).radmaskexp);
    float chromaskexpo = ((float) locallab.spots.at(sp).chromaskexp);
    float gammaskexpo = ((float) locallab.spots.at(sp).gammaskexp);
    float slomaskexpo = ((float) locallab.spots.at(sp).slomaskexp);
    float strmaskexpo = ((float) locallab.spots.at(sp).strmaskexp);
    float angmaskexpo = ((float) locallab.spots.at(sp).angmaskexp);
    float strmask = ((float) locallab.spots.at(sp).str_mask);
    float angmask = ((float) locallab.spots.at(sp).ang_mask);
    float feathermask = ((float) locallab.spots.at(sp).feather_mask);
    float strexpo = ((float) locallab.spots.at(sp).strexp);
    float angexpo = ((float) locallab.spots.at(sp).angexp);
    float featherexpo = ((float) locallab.spots.at(sp).featherexp);
    float strSH = ((float) locallab.spots.at(sp).strSH);
    float angSH = ((float) locallab.spots.at(sp).angSH);
    float featherSH = ((float) locallab.spots.at(sp).featherSH);
    float strcol = ((float) locallab.spots.at(sp).strcol);
    float strcolab = ((float) locallab.spots.at(sp).strcolab);
    float strcolh = ((float) locallab.spots.at(sp).strcolh);
    float angcol = ((float) locallab.spots.at(sp).angcol);
    float feathcol = ((float) locallab.spots.at(sp).feathercol);
    float strvib = ((float) locallab.spots.at(sp).strvib);
    float strvibab = ((float) locallab.spots.at(sp).strvibab);
    float strvibh = ((float) locallab.spots.at(sp).strvibh);
    float angvib = ((float) locallab.spots.at(sp).angvib);
    float feathervib = ((float) locallab.spots.at(sp).feathervib);
    float strwav = ((float) locallab.spots.at(sp).strwav);
    float angwav = ((float) locallab.spots.at(sp).angwav);
    float featherwav = ((float) locallab.spots.at(sp).featherwav);
    float strlog = ((float) locallab.spots.at(sp).strlog);
    float anglog = ((float) locallab.spots.at(sp).anglog);
    float featherlog = ((float) locallab.spots.at(sp).featherlog);
    float softradiusexpo = ((float) locallab.spots.at(sp).softradiusexp);
    float softradiuscolor = ((float) locallab.spots.at(sp).softradiuscol);
    float softradiusreti = ((float) locallab.spots.at(sp).softradiusret);
    float softradiustma = ((float) locallab.spots.at(sp).softradiustm);
    float softradiuscbdl = ((float) locallab.spots.at(sp).softradiuscb);
    float blendmaskSH = ((float) locallab.spots.at(sp).blendmaskSH) / 100.f ;
    float radmaskSH = ((float) locallab.spots.at(sp).radmaskSH);
    float chromaskSH = ((float) locallab.spots.at(sp).chromaskSH);
    float gammaskSH = ((float) locallab.spots.at(sp).gammaskSH);
    float slomaskSH = ((float) locallab.spots.at(sp).slomaskSH);
    float blendmaskvib = ((float) locallab.spots.at(sp).blendmaskvib) / 100.f ;
    float radmaskvib = ((float) locallab.spots.at(sp).radmaskvib);
    float chromaskvib = ((float) locallab.spots.at(sp).chromaskvib);
    float gammaskvib = ((float) locallab.spots.at(sp).gammaskvib);
    float slomaskvib = ((float) locallab.spots.at(sp).slomaskvib);
    float structexpo = (float) locallab.spots.at(sp).structexp;
    float blurexpo = (float) locallab.spots.at(sp).blurexpde;
    float blurcolor = (float) locallab.spots.at(sp).blurcolde;
    float blurcolmask = (float) locallab.spots.at(sp).blurcol;
    float contcolmask = (float) locallab.spots.at(sp).contcol;
    float blurSH = (float) locallab.spots.at(sp).blurSHde;
    float local_transit = locallab.spots.at(sp).transit;
    float local_feather = locallab.spots.at(sp).feather;
    float local_transitweak = (float)locallab.spots.at(sp).transitweak;
    float local_transitgrad = (float)locallab.spots.at(sp).transitgrad;
    float radius = (float) locallab.spots.at(sp).radius;
    int itera = locallab.spots.at(sp).itera;
    int guidbl = locallab.spots.at(sp).guidbl;


    float denocontrast = (float)locallab.spots.at(sp).denocontrast;
    float denoratio = (float)locallab.spots.at(sp).denoratio;
    float denomask = (float)locallab.spots.at(sp).denomask;
    bool contrshow =  locallab.spots.at(sp).contrshow;
    bool denoAutocontrast =  locallab.spots.at(sp).denoAutocontrast;
    bool enacontra =  locallab.spots.at(sp).enacontrast;

    lp.denocontra = denocontrast;
    lp.denorati = 0.01f * denoratio;
    lp.denomas = 0.01f * denomask;
    lp.contrsho = contrshow; 
    lp.denoAutocontr = denoAutocontrast;
    lp.enacontr = enacontra; 
    float epsbl = (float) locallab.spots.at(sp).epsbl;
    float sharradius = LIM(locallab.spots.at(sp).sharradius, 0.42, 3.5);
    float lcamount = ((float) locallab.spots.at(sp).lcamount);
    lcamount = LIM01(lcamount); //to prevent crash with old pp3 integer
    float sharblurr = LIM(locallab.spots.at(sp).sharblur, 0.2, 3.); //to prevent crash with old pp3 integer
    int local_sensisha = locallab.spots.at(sp).sensisha;
    int local_sharamount = locallab.spots.at(sp).sharamount;
    int local_shardamping = locallab.spots.at(sp).shardamping;
    int local_shariter = locallab.spots.at(sp).shariter;
    bool inverse = locallab.spots.at(sp).invers;
    bool curvacti = locallab.spots.at(sp).curvactiv;
    bool acti = locallab.spots.at(sp).activlum;
    bool cupas = false; // Provision
    int local_sensisf = locallab.spots.at(sp).sensisf;
    bool inverseex = locallab.spots.at(sp).inversex;
    bool inversesh = locallab.spots.at(sp).inverssh;
    bool equiltm = locallab.spots.at(sp).equiltm;
    bool fftwlc = locallab.spots.at(sp).fftwlc;
    bool fftwreti = locallab.spots.at(sp).fftwreti;

    float blendmaskL = ((float) locallab.spots.at(sp).blendmaskL) / 100.f ;
    float radmaskL = ((float) locallab.spots.at(sp).radmaskL);
    float chromaskL = ((float) locallab.spots.at(sp).chromaskL);

    bool equilret = locallab.spots.at(sp).equilret;
    bool inverserad = false; // Provision
    bool inverseret = locallab.spots.at(sp).inversret;
    bool inversesha = locallab.spots.at(sp).inverssha;
    double strength = (double) locallab.spots.at(sp).strength;
    float str = (float)locallab.spots.at(sp).str;
    int scaleret = (float)locallab.spots.at(sp).scalereti;

    int local_sensihs = locallab.spots.at(sp).sensihs;
    int highhs = locallab.spots.at(sp).highlights;
    int hltonahs = locallab.spots.at(sp).h_tonalwidth;
    int shadhs = locallab.spots.at(sp).shadows;
    int shtonals = locallab.spots.at(sp).s_tonalwidth;
    int radhs = locallab.spots.at(sp).sh_radius;
    float blendmaskcb = ((float) locallab.spots.at(sp).blendmaskcb) / 100.f ;
    float radmaskcb = ((float) locallab.spots.at(sp).radmaskcb);
    float chromaskcb = ((float) locallab.spots.at(sp).chromaskcb);
    float gammaskcb = ((float) locallab.spots.at(sp).gammaskcb);
    float slomaskcb = ((float) locallab.spots.at(sp).slomaskcb);
    bool enaretiMasktm = locallab.spots.at(sp).enaretiMasktmap;
    lp.enaretiMasktmap =  enaretiMasktm;
    float blendmasktm = ((float) locallab.spots.at(sp).blendmasktm) / 100.f ;
    float radmasktm = ((float) locallab.spots.at(sp).radmasktm);
    float chromasktm = ((float) locallab.spots.at(sp).chromasktm);
    float gammasktm = ((float) locallab.spots.at(sp).gammasktm);
    float slomasktm = ((float) locallab.spots.at(sp).slomasktm);
    bool wavgradl = locallab.spots.at(sp).wavgradl;

    float blendmaskbl = ((float) locallab.spots.at(sp).blendmaskbl) / 100.f ;
    float radmaskbl = ((float) locallab.spots.at(sp).radmaskbl);
    float chromaskbl = ((float) locallab.spots.at(sp).chromaskbl);
    float gammaskbl = ((float) locallab.spots.at(sp).gammaskbl);
    float slomaskbl = ((float) locallab.spots.at(sp).slomaskbl);
    bool fftbl = locallab.spots.at(sp).fftwbl;


    lp.sourcegray = (float) locallab.spots.at(sp).sourceGray;
    lp.targetgray = (float) locallab.spots.at(sp).targetGray;
    lp.blackev = (float) locallab.spots.at(sp).blackEv;
    lp.whiteev  = (float) locallab.spots.at(sp).whiteEv;
    lp.sourcegraycie = (float) locallab.spots.at(sp).sourceGraycie;
    lp.targetgraycie = (float) locallab.spots.at(sp).targetGraycie;
    
    if (locallab.spots.at(sp).modecam == "cam16") {
        lp.moka = 1;
    } else if (locallab.spots.at(sp).modecam == "jz") {
        lp.moka = 2;
    }

    if (locallab.spots.at(sp).sursourcie == "Average") {
        lp.sursouci = 0;
    } else if (locallab.spots.at(sp).sursourcie == "Dim") {
        lp.sursouci = 1;
    } else if (locallab.spots.at(sp).sursourcie == "Dark") {
        lp.sursouci = 2;
    } else if (locallab.spots.at(sp).sursourcie == "exDark") {
        lp.sursouci = 3;
    } else if (locallab.spots.at(sp).sursourcie == "disacie") {
        lp.sursouci = 4;
    }

    lp.satcie = (float) locallab.spots.at(sp).satcie;
    lp.satlog = (float) locallab.spots.at(sp).satlog;
    lp.blackevjz = (float) locallab.spots.at(sp).blackEvjz;
    lp.whiteevjz  = (float) locallab.spots.at(sp).whiteEvjz;
    lp.detail = locallab.spots.at(sp).detail;
    lp.detailcie = 0.01 * locallab.spots.at(sp).detailcie;
    lp.sensilog = locallab.spots.at(sp).sensilog;
    lp.Autogray = locallab.spots.at(sp).Autogray;
    lp.autocompute = locallab.spots.at(sp).autocompute;
    lp.baselog = (float) locallab.spots.at(sp).baselog;
    lp.sensimas = locallab.spots.at(sp).sensimask;
    lp.sensicie = locallab.spots.at(sp).sensicie;
    float blendmaskcie = ((float) locallab.spots.at(sp).blendmaskcie) / 100.f ;
    float radmaskcie = ((float) locallab.spots.at(sp).radmaskcie);
    float chromaskcie = ((float) locallab.spots.at(sp).chromaskcie);
    float blurciemask = (float) locallab.spots.at(sp).blurcie;
    float contciemask = (float) locallab.spots.at(sp).contcie;
    float strgradcie = ((float) locallab.spots.at(sp).strgradcie);
    float anggradcie = ((float) locallab.spots.at(sp).anggradcie);
    float feathercie = ((float) locallab.spots.at(sp).feathercie);

    lp.comprlo = locallab.spots.at(sp).comprlog;
    lp.comprlocie = locallab.spots.at(sp).comprcie;


    lp.deltaem = locallab.spots.at(sp).deltae;
    lp.scalereti = scaleret;
    lp.cir = circr;
    lp.recur = recur;
    lp.actsp = acti;
    lp.xc = w * local_center_x;
    lp.yc = h * local_center_y;
    lp.xcent = local_center_x;
    lp.ycent = local_center_y;
    lp.lx = w * local_x;
    lp.ly = h * local_y;
    lp.lxL = w * local_xL;
    lp.lyT = h * local_yT;
    lp.chro = local_chroma;
    lp.struco = structcolor;
    lp.strengrid = strengthgrid;
    lp.blendmalc = blendmasklc;
    lp.radmalc = radmasklc;
    lp.chromalc = chromasklc;
    lp.blendmacol = blendmaskcolor;
    lp.radmacol = radmaskcolor;
    lp.chromacol = chromaskcolor;
    lp.gammacol = gammaskcolor;
    lp.slomacol = slomaskcolor;
    lp.radmaexp = radmaskexpo;
    lp.chromaexp = chromaskexpo;
    lp.gammaexp = gammaskexpo;
    lp.slomaexp = slomaskexpo;
    lp.strmaexp = strmaskexpo;
    lp.angmaexp = angmaskexpo;
    lp.str_mas = strmask;
    lp.ang_mas = angmask;
    lp.feather_mas = feathermask;

    lp.strexp = strexpo;
    lp.angexp = angexpo;
    lp.featherexp = featherexpo;
    lp.strSH = strSH;
    lp.angSH = angSH;
    lp.featherSH = featherSH;
    lp.strcol = strcol;
    lp.strcolab = strcolab;
    lp.strcolh = strcolh;
    lp.angcol = angcol;
    lp.feathcol = feathcol;
    lp.strvib = strvib;
    lp.strvibab = strvibab;
    lp.strvibh = strvibh;
    lp.angvib = angvib;
    lp.feathervib = feathervib;
    lp.strwav = strwav;
    lp.angwav = angwav;
    lp.featherwav = featherwav;
    lp.strlog = strlog;
    lp.anglog = anglog;
    lp.featherlog = featherlog;
    lp.softradiusexp = softradiusexpo;
    lp.softradiuscol = softradiuscolor;
    lp.softradiusret = softradiusreti;
    lp.softradiuscb = softradiuscbdl;
    lp.softradiustm = softradiustma;
    lp.struexc = structexclude;
    lp.blendmaexp = blendmaskexpo;
    lp.blendmaSH = blendmaskSH;
    lp.radmaSH = radmaskSH;
    lp.chromaSH = chromaskSH;
    lp.gammaSH = gammaskSH;
    lp.slomaSH = slomaskSH;
    lp.blendmavib = blendmaskvib;
    lp.radmavib = radmaskvib;
    lp.chromavib = chromaskvib;
    lp.gammavib = gammaskvib;
    lp.slomavib = slomaskvib;
    lp.blendmacb = blendmaskcb;
    lp.radmacb = radmaskcb;
    lp.chromacbm = chromaskcb;
    lp.gammacb = gammaskcb;
    lp.slomacb = slomaskcb;
    lp.blendmatm = blendmasktm;
    lp.radmatm = radmasktm;
    lp.chromatm = chromasktm;
    lp.gammatm = gammasktm;
    lp.slomatm = slomasktm;
    lp.wavgradl = wavgradl;
    lp.blendmaL = blendmaskL;
    lp.radmaL = radmaskL;
    lp.chromaL = chromaskL;

    lp.strengthw = ((float) locallab.spots.at(sp).strengthw);
    lp.radiusw = ((float) locallab.spots.at(sp).radiusw);
    lp.detailw = ((float) locallab.spots.at(sp).detailw);
    lp.gradw = ((float) locallab.spots.at(sp).gradw);
    lp.tloww = ((float) locallab.spots.at(sp).tloww);
    lp.thigw = ((float) locallab.spots.at(sp).thigw);
    lp.edgw = ((float) locallab.spots.at(sp).edgw);
    lp.basew = ((float) locallab.spots.at(sp).basew);

    lp.blendmabl = blendmaskbl;
    lp.radmabl = radmaskbl;
    lp.chromabl = chromaskbl;
    lp.gammabl = gammaskbl;
    lp.slomabl = slomaskbl;
    lp.fftbl = fftbl;
    lp.it = itera;
    lp.guidb = guidbl;
    lp.strbl = 0.01f * (float) locallab.spots.at(sp).strbl;

    lp.epsb = epsbl;
    lp.struexp = structexpo;
    lp.blurexp = blurexpo;
    lp.blurcol = blurcolor;
    lp.blurcolmask = blurcolmask;
    lp.contcolmask = 0.01f * contcolmask;
    lp.blurSH = blurSH;
    lp.sens = local_sensi;
    lp.sensh = local_sensih;
    lp.dehaze = local_dehaze;
    lp.dehazeSaturation = local_dehazeSaturation;
    lp.depth = local_depth;
    lp.senscb = local_sensicb;
    lp.clarityml = local_clarityml;
    lp.contresid = local_contresid;
    lp.cont = local_contrast;
    lp.ligh = local_lightness;
    lp.lowA = labgridALowloc;
    lp.lowB = labgridBLowloc;
    lp.highB = labgridBHighloc;
    lp.highA = labgridAHighloc;
    lp.lowBmerg = labgridBLowlocmerg;
    lp.highBmerg = labgridBHighlocmerg;
    lp.lowAmerg = labgridALowlocmerg;
    lp.highAmerg = labgridAHighlocmerg;
    lp.gamlc = local_gamlc;
    lp.gamc = local_gamc;
    lp.gamex = local_gamex;

    lp.senssf = local_sensisf;
    lp.strng = strlight;
    lp.neig = neigh;
    lp.lap = laplac;

    if (lp.ligh >= -2.f && lp.ligh <= 2.f) {
        lp.ligh /= 5.f;
    }

    lp.trans = local_transit;
    lp.feath = local_feather;
    lp.transweak = local_transitweak;
    lp.transgrad = local_transitgrad;
    lp.rad = radius;
    lp.stren = strength;
    lp.sensbn = local_sensibn;
    lp.sensexclu = local_sensiexclu;
    lp.senslc = local_sensilc;
    lp.lcamount = lcamount;
    lp.inv = inverse;
    lp.invex = inverseex;
    lp.invsh = inversesh;
    lp.curvact = curvacti;
    lp.invrad = inverserad;
    lp.invret = inverseret;
    lp.equret = equilret;
    lp.equtm = equiltm;
    lp.invshar = inversesha;
    lp.str = str;
    lp.shrad = sharradius;
    lp.shblurr = sharblurr;
    lp.senssha = local_sensisha;
    lp.shamo = local_sharamount;
    lp.shdamp = local_shardamping;
    lp.shiter = local_shariter;
    lp.iterat = iterati;
    lp.balance = balanc;
    lp.balanceh = balanch;
    lp.colorde = colorde;
    lp.thr = thre;
    lp.stru = strucc;
    lp.noiself = local_noiself;
    lp.noiself0 = local_noiself0;
    lp.noiself2 = local_noiself2;
    lp.noiseldetail = local_noiseldetail;
    lp.detailthr = local_detailthr;
    lp.recothr = local_recothr;
    lp.lowthr = local_lowthr;
    lp.higthr = local_higthr;
    lp.recothrd = local_recothrd;
    lp.midthrd = local_midthrd;
    lp.midthrdch = local_midthrdch;
    lp.lowthrd = local_lowthrd;
    lp.higthrd = local_higthrd;
    lp.decayd = local_decayd;
    lp.recothrc = local_recothrc;
    lp.lowthrc = local_lowthrc;
    lp.higthrc = local_higthrc;
    lp.decayc = local_decayc;
    lp.recothre = local_recothre;
    lp.lowthre = local_lowthre;
    lp.higthre = local_higthre;
    lp.decaye = local_decaye;
    lp.recothrs = local_recothrs;
    lp.lowthrs = local_lowthrs;
    lp.higthrs = local_higthrs;
    lp.decays = local_decays;

    lp.recothrcie = local_recothrcie;
    lp.lowthrcie = local_lowthrcie;
    lp.higthrcie = local_higthrcie;
    lp.decaycie = local_decaycie;

    lp.recothrv = local_recothrv;
    lp.lowthrv = local_lowthrv;
    lp.higthrv = local_higthrv;
    lp.decayv = local_decayv;
    lp.recothrw = local_recothrw;
    lp.lowthrw = local_lowthrw;
    lp.higthrw = local_higthrw;
    lp.decayw = local_decayw;
    lp.recothrt = local_recothrt;
    lp.lowthrt = local_lowthrt;
    lp.higthrt = local_higthrt;
    lp.decayt = local_decayt;
    lp.recothrcb = local_recothrcb;
    lp.lowthrcb = local_lowthrcb;
    lp.higthrcb = local_higthrcb;
    lp.decaycb = local_decaycb;
    lp.recothrr = local_recothrr;
    lp.lowthrr = local_lowthrr;
    lp.higthrr = local_higthrr;
    lp.decayr = local_decayr;

    lp.recothrl = local_recothrl;
    lp.lowthrl = local_lowthrl;
    lp.higthrl = local_higthrl;
    lp.decayl = local_decayl;
    lp.noiselequal = local_noiselequal;
    lp.noisechrodetail = local_noisechrodetail;
    lp.noiselc = local_noiselc;
    lp.noiselc4 = lnoiselc4;
    lp.noiselc5 = lnoiselc5;
    lp.noiselc6 = lnoiselc6;

    lp.noisecf = local_noisecf;
    lp.noisecc = local_noisecc;
    lp.sensden = local_sensiden;
    lp.reparden = local_reparden;
    lp.repartm = local_repartm;
    lp.bilat = locallab.spots.at(sp).bilateral;
    lp.nldet = locallab.spots.at(sp).nldet;
    lp.nlstr = locallab.spots.at(sp).nlstr;
    lp.nlpat = locallab.spots.at(sp).nlpat;
    lp.nlrad = locallab.spots.at(sp).nlrad;
    lp.nlgam = locallab.spots.at(sp).nlgam;
    lp.nliter = locallab.spots.at(sp).nliter;
    lp.noisegam = locallab.spots.at(sp).noisegam;
    lp.adjch = (float) locallab.spots.at(sp).adjblur;
    lp.strengt = streng;
    lp.gamm = gam;
    lp.esto = est;
    lp.scalt = scal_tm;
    lp.rewe = rewe;
    lp.senstm = local_sensitm;
    lp.amo = amo;
    lp.blurma = (float) locallab.spots.at(sp).blurmask;
    lp.fftma = locallab.spots.at(sp).fftmask;
    lp.contma = (float) locallab.spots.at(sp).contmask;
    lp.blurciemask = blurciemask;
    lp.contciemask = 0.01f * contciemask;
    lp.strgradcie = strgradcie;
    lp.anggradcie = anggradcie;
    lp.feathercie = feathercie;

    lp.blendmacie = blendmaskcie;
    lp.radmacie = radmaskcie;
    lp.chromacie = chromaskcie;
    lp.denoichmask = locallab.spots.at(sp).denoichmask;

    for (int y = 0; y < 6; y++) {
        lp.mulloc[y] = LIM(multi[y], 0.f, 4.f);//to prevent crash with old pp3 integer
    }

    for (int y = 0; y < 6; y++) {
        lp.mullocsh[y] = multish[y];
    }

    lp.activspot = locallab.spots.at(sp).activ;
    lp.detailsh = locallab.spots.at(sp).detailSH; 
    lp.tePivot = locallab.spots.at(sp).tePivot;
    lp.whitescie = locallab.spots.at(sp).whitescie;
    lp.midtcie = locallab.spots.at(sp).midtcie; 
    
    lp.threshol = thresho;
    lp.chromacb = chromcbdl;
    lp.expvib = locallab.spots.at(sp).expvibrance && lp.activspot ;
    lp.colorena = locallab.spots.at(sp).expcolor && lp.activspot && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0; // Color & Light tool is deactivated if Exposure mask is visible or SHMask
    lp.blurena = locallab.spots.at(sp).expblur && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && llColorMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.tonemapena = locallab.spots.at(sp).exptonemap && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && llColorMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.retiena = locallab.spots.at(sp).expreti && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llColorMask == 0 && lltmMask == 0 && llvibMask == 0 && llSHMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.lcena = locallab.spots.at(sp).expcontrast && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llcbMask == 0 && llsharMask == 0 && llColorMask == 0 && lltmMask == 0 && llvibMask == 0 && llSHMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.cbdlena = locallab.spots.at(sp).expcbdl && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llExpMask == 0 && llsoftMask == 0 && llSHMask == 0 && llretiMask == 0 && lllcMask == 0 && llsharMask == 0 && lllcMask == 0 && llColorMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.exposena = locallab.spots.at(sp).expexpose  && lp.activspot  && llColorMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llSHMask == 0 && lllcMask == 0 && llsharMask == 0 && llcbMask == 0 && llretiMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0; // Exposure tool is deactivated if Color & Light mask SHmask is visible
    lp.hsena = locallab.spots.at(sp).expshadhigh && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// Shadow Highlight tool is deactivated if Color & Light mask or SHmask is visible
    lp.vibena = locallab.spots.at(sp).expvibrance && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && llcbMask == 0 && lltmMask == 0 && llSHMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;// vibrance tool is deactivated if Color & Light mask or SHmask is visible
    lp.sharpena = locallab.spots.at(sp).expsharp && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llretiMask == 0 && llcbMask == 0 && lltmMask == 0 && llSHMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.sfena = locallab.spots.at(sp).expsoft && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llretiMask == 0 && llcbMask == 0 && lltmMask == 0 && llSHMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0 && llcieMask == 0;
    lp.maskena = locallab.spots.at(sp).expmask && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && llcbMask == 0 && lltmMask == 0 && lllogMask == 0 && llSHMask == 0 && llcieMask == 0;// vibrance tool is deactivated if Color & Light mask or SHmask is visible
    lp.logena = locallab.spots.at(sp).explog && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && llcbMask == 0 && lltmMask == 0 && llSHMask == 0 && ll_Mask == 0 && llcieMask == 0;// vibrance tool is deactivated if Color & Light mask or SHmask is visible
    lp.cieena = locallab.spots.at(sp).expcie && lp.activspot && llColorMaskinv == 0 && llExpMaskinv == 0 && llSHMaskinv == 0 && llColorMask == 0 && llsoftMask == 0 && llExpMask == 0 && llcbMask == 0 && lllcMask == 0 && llsharMask == 0 && llretiMask == 0 && lltmMask == 0 && llvibMask == 0 && lllogMask == 0 && ll_Mask == 0;// Shadow Highlight tool is deactivated if Color & Light mask or SHmask is visible


    lp.islocal = (lp.expvib || lp.colorena || lp.blurena || lp.tonemapena || lp.retiena || lp.lcena || lp.cbdlena || lp.exposena || lp.hsena || lp.vibena || lp.sharpena || lp.sfena || lp.maskena || lp.logena || lp.cieena);

    lp.sensv = local_sensiv;
    lp.past =  chromaPastel;
    lp.satur = chromaSatur;

    lp.cut_past = cupas;
    lp.blac = locallab.spots.at(sp).black;
    lp.shcomp = locallab.spots.at(sp).shcompr;
    lp.shadex = locallab.spots.at(sp).shadex;
    lp.hlcomp = locallab.spots.at(sp).hlcompr;
    lp.hlcompthr = locallab.spots.at(sp).hlcomprthresh;
    lp.expcomp = LIM(locallab.spots.at(sp).expcomp, -2.0, 4.0); //to prevent crash with Old pp3 with integer
    lp.expchroma = locallab.spots.at(sp).expchroma / 100.;
    lp.sensex = local_sensiex;
    lp.war = local_warm;
    lp.highlihs = highhs;
    lp.shadowhs = shadhs;
    lp.radiushs = radhs;
    lp.hltonalhs = hltonahs;
    lp.shtonalhs = shtonals;
    lp.senshs = local_sensihs;
    lp.ftwlc = fftwlc;
    lp.ftwreti = fftwreti;
    lp.sigmadr = locallab.spots.at(sp).sigmadr;
    lp.sigmabl = locallab.spots.at(sp).sigmabl;
    lp.sigmaed = locallab.spots.at(sp).sigmaed;
    lp.sigmalc = locallab.spots.at(sp).sigmalc;
    lp.offslc = locallab.spots.at(sp).offslc;
    lp.sigmalc2 = locallab.spots.at(sp).sigmalc2;
    lp.residsha = locallab.spots.at(sp).residsha;
    lp.residshathr = locallab.spots.at(sp).residshathr;
    lp.residhi = locallab.spots.at(sp).residhi;
    lp.residhithr = locallab.spots.at(sp).residhithr;
    lp.residgam = locallab.spots.at(sp).residgam;
    lp.residslop = locallab.spots.at(sp).residslop;
    lp.avoidneg = locallab.spots.at(sp).avoidneg;
    lp.blwh = locallab.spots.at(sp).blwh;
    lp.senscolor = (int) locallab.spots.at(sp).colorscope;
    //replace scope color vibrance shadows
/*    lp.sens = lp.senscolor;
    lp.sensv = lp.senscolor;
    lp.senshs = lp.senscolor;
*/
    lp.mLjz = locallab.spots.at(sp).clarilresjz / 100.0;
    lp.mCjz = locallab.spots.at(sp).claricresjz / 100.0;
    lp.softrjz = locallab.spots.at(sp).clarisoftjz;

}

static void calcTransitionrect(const float lox, const float loy, const float ach, const local_params& lp, int &zone, float &localFactor)
{
    zone = 0;

    if (lox >= lp.xc && lox < lp.xc + lp.lx) {
        if (loy >= lp.yc && loy < lp.yc + lp.ly) {
            if (lox < lp.xc + lp.lx * ach && loy < lp.yc + lp.ly * ach) {
                zone = 2;
            } else {
                zone = 1;
                localFactor = pow_F(calcLocalFactorrect(lox, loy, lp.xc, lp.lx, lp.yc, lp.ly, ach, lp.transgrad), lp.transweak);
            }
        } else if (loy < lp.yc && loy > lp.yc - lp.lyT) {
            if (lox < lp.xc + lp.lx * ach && loy > lp.yc - lp.lyT * ach) {
                zone = 2;
            } else {
                zone = 1;
                localFactor = pow_F(calcLocalFactorrect(lox, loy, lp.xc, lp.lx, lp.yc, lp.lyT, ach, lp.transgrad), lp.transweak);
            }
        }
    } else if (lox < lp.xc && lox > lp.xc - lp.lxL) {
        if (loy <= lp.yc && loy > lp.yc - lp.lyT) {
            if (lox > (lp.xc - lp.lxL * ach) && loy > (lp.yc - lp.lyT * ach)) {
                zone = 2;
            } else {
                zone = 1;
                localFactor = pow_F(calcLocalFactorrect(lox, loy, lp.xc, lp.lxL, lp.yc, lp.lyT, ach, lp.transgrad), lp.transweak);
            }
        } else if (loy > lp.yc && loy < lp.yc + lp.ly) {
            if (lox > (lp.xc - lp.lxL * ach) && loy < (lp.yc + lp.ly * ach)) {
                zone = 2;
            } else {
                zone = 1;
                localFactor = pow_F(calcLocalFactorrect(lox, loy, lp.xc, lp.lxL, lp.yc, lp.ly, ach, lp.transgrad), lp.transweak);
            }
        }
    }
}

static void calcTransition(const float lox, const float loy, const float ach, const local_params& lp, int &zone, float &localFactor)
{
    // returns the zone (0 = outside selection, 1 = transition zone between outside and inside selection, 2 = inside selection)
    // and a factor to calculate the transition in case zone == 1

    zone = 0;

    if (lox >= lp.xc && lox < lp.xc + lp.lx) {
        if (loy >= lp.yc && loy < lp.yc + lp.ly) {
            const float zoneVal = SQR((lox - lp.xc) / (ach * lp.lx)) + SQR((loy - lp.yc) / (ach * lp.ly));
            zone = zoneVal < 1.f ? 2 : 0;

            if (!zone) {
                zone = (zoneVal > 1.f && ((SQR((lox - lp.xc) / (lp.lx)) + SQR((loy - lp.yc) / (lp.ly))) < 1.f)) ? 1 : 0;

                if (zone == 1) {
                    localFactor = pow_F(calcLocalFactor(lox, loy, lp.xc, lp.lx, lp.yc, lp.ly, ach, lp.transgrad), lp.transweak);
                }
            }
        } else if (loy < lp.yc && loy > lp.yc - lp.lyT) {
            const float zoneVal = SQR((lox - lp.xc) / (ach * lp.lx)) + SQR((loy - lp.yc) / (ach * lp.lyT));
            zone = zoneVal < 1.f ? 2 : 0;

            if (!zone) {
                zone = (zoneVal > 1.f && ((SQR((lox - lp.xc) / (lp.lx)) + SQR((loy - lp.yc) / (lp.lyT))) < 1.f)) ? 1 : 0;

                if (zone == 1) {
                    localFactor = pow_F(calcLocalFactor(lox, loy, lp.xc, lp.lx, lp.yc, lp.lyT, ach, lp.transgrad), lp.transweak);
                }
            }
        }
    } else if (lox < lp.xc && lox > lp.xc - lp.lxL) {
        if (loy <= lp.yc && loy > lp.yc - lp.lyT) {
            const float zoneVal = SQR((lox - lp.xc) / (ach * lp.lxL)) + SQR((loy - lp.yc) / (ach * lp.lyT));
            zone = zoneVal < 1.f ? 2 : 0;

            if (!zone) {
                zone = (zoneVal > 1.f && ((SQR((lox - lp.xc) / (lp.lxL)) + SQR((loy - lp.yc) / (lp.lyT))) < 1.f)) ? 1 : 0;

                if (zone == 1) {
                    localFactor = pow_F(calcLocalFactor(lox, loy, lp.xc, lp.lxL, lp.yc, lp.lyT, ach, lp.transgrad), lp.transweak);
                }
            }
        } else if (loy > lp.yc && loy < lp.yc + lp.ly) {
            const float zoneVal = SQR((lox - lp.xc) / (ach * lp.lxL)) + SQR((loy - lp.yc) / (ach * lp.ly));
            zone = zoneVal < 1.f ? 2 : 0;

            if (!zone) {
                zone = (zoneVal > 1.f && ((SQR((lox - lp.xc) / (lp.lxL)) + SQR((loy - lp.yc) / (lp.ly))) < 1.f)) ? 1 : 0;

                if (zone == 1) {
                    localFactor = pow_F(calcLocalFactor(lox, loy, lp.xc, lp.lxL, lp.yc, lp.ly, ach, lp.transgrad), lp.transweak);
                }
            }
        }
    }
}
//sigmoid
/* C -*
 * 
 * Simplified porting of darktable's sigmoid module to Rawtherapee 
 * Copyright of the original code follows
 * Thanks to Alberto Griggio for the Sigmoid Tone Mapper CTL
 */
/*
    This file is part of darktable,
    Copyright (C) 2020-2023 darktable developers.

    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 <http://www.gnu.org/licenses/>.
*/

//const float middle_grey = 0.01f * params->locallab.spots.at(sp).sourceGraycie; //0.1845f;

const float display_black_target = 0.0152f;

const float display_white_target = 100.f;

float max(float a, float b)
{
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

float generalized_loglogistic_sigmoid(float value,
                                      float magnitude,
                                      float paper_exp,
                                      float film_fog,
                                      float film_power,
                                      float paper_power)
{
    const float clamped_value = max(value, 0.0);
    // The following equation can be derived as a model for film + paper but it has a pole at 0
    // magnitude * powf(1.0f + paper_exp * powf(film_fog + value, -film_power), -paper_power);
    // Rewritten on a stable around zero form:
    const float film_response = pow(film_fog + clamped_value, film_power);
    const float paper_response = magnitude * pow(film_response / (paper_exp + film_response), paper_power);

    // Safety check for very large floats that cause numerical errors
    if (xisnanf(paper_response)) {
        return magnitude;
    } else {
        return paper_response;
    }
}

void calculate_params(float middle_grey_contrast,
                      float contrast_skewness,
                      float display_black_target,
                     // float display_white_target,
                      float &film_power,
                      float &white_target,
                      float &black_target,
                      float &film_fog,
                      float &paper_exposure,
                      float &paper_power,
                      float middle_grey
                     // float display_white_target = 1.f
)
{
    /* Calculate actual skew log logistic parameters to fulfill the following:
     * f(scene_zero) = display_black_target
     * f(scene_grey) = middle_grey
     * f(scene_inf)  = display_white_target
     * Slope at scene_grey independent of skewness i.e. only changed by the contrast parameter.
     */

    // Calculate a reference slope for no skew and a normalized display
    const float ref_film_power = middle_grey_contrast;
    const float ref_paper_power = 1.0f;
    const float ref_magnitude = 1.0f;
    const float ref_film_fog = 0.0f;
    const float ref_paper_exposure =  pow_F(ref_film_fog + middle_grey, ref_film_power) * ((ref_magnitude / middle_grey) - 1.0f);
    const float delta = 1e-6;
    const float ref_slope  = (generalized_loglogistic_sigmoid(middle_grey + delta, ref_magnitude, ref_paper_exposure, ref_film_fog,
                                           ref_film_power, ref_paper_power)
           - generalized_loglogistic_sigmoid(middle_grey - delta, ref_magnitude, ref_paper_exposure, ref_film_fog,
                                             ref_film_power, ref_paper_power)) / 2.0f / delta;

    // Add skew
    paper_power = pow(5.0, -contrast_skewness);

    // Slope at low film power
    const float temp_film_power = 1.0f;
    const float temp_white_target = 0.01f * white_target; //display_white_target;
    const float temp_white_grey_relation = pow_F(temp_white_target / middle_grey, 1.0f / paper_power) - 1.0f;
    const float temp_paper_exposure = pow_F(middle_grey, temp_film_power) * temp_white_grey_relation;
    const float temp_slope  = (generalized_loglogistic_sigmoid(middle_grey + delta, temp_white_target, temp_paper_exposure,
                                           ref_film_fog, temp_film_power, paper_power)
           - generalized_loglogistic_sigmoid(middle_grey - delta, temp_white_target, temp_paper_exposure,
                                             ref_film_fog, temp_film_power, paper_power)) / 2.0f / delta;

    // Figure out what film power fulfills the target slope
    // (linear when assuming display_black = 0.0)
    film_power = ref_slope / temp_slope;

    // Calculate the other parameters now that both film and paper power is known
    white_target = 0.01f * white_target; //display_white_target;
    black_target = 0.01f * display_black_target;
    const float white_grey_relation = pow_F(white_target / middle_grey, 1.0f / paper_power) - 1.0f;
    const float white_black_relation = pow_F(black_target / white_target, -1.0f / paper_power) - 1.0f;

    film_fog = middle_grey * pow(white_grey_relation, 1.0f / film_power) / (pow_F(white_black_relation, 1.0f / film_power) - pow_F(white_grey_relation, 1.0f / film_power));
    paper_exposure = pow_F(film_fog + middle_grey, film_power) * white_grey_relation;
}


void  ImProcFunctions::sigmoid_main(float r,
              float g,
              float b,
              float &rout,
              float &gout,
              float &bout,
              float middle_grey_contrast,
              float contrast_skewness,
         //     float white_point,
              float middle_grey,
              float black_point,
              float white_point_disp)
{
    float film_power = 1.f;
    float white_target = white_point_disp;
    float black_target = 1.f;
    float film_fog = 1.f;
    float paper_exposure = 1.f;
    float paper_power = 1.f;
    float display_black_target = black_point; //0.0152f;

//
    // compute the sigmoid parameters from the UI controls
    calculate_params(middle_grey_contrast, contrast_skewness,
                     display_black_target,  film_power,
                     white_target, black_target, film_fog,
                     paper_exposure, paper_power, middle_grey);
    float rgb[3] = {r, g, b};
    for (int i = 0; i < 3; i = i+1) {
        rgb[i] = max(rgb[i], 0);
    }
    for (int i = 0; i < 3; i = i+1) {
        rgb[i] = generalized_loglogistic_sigmoid(rgb[i], white_target,
                                                 paper_exposure, film_fog,
                                                 film_power, paper_power);
    }
    rout = rgb[0];
    gout = rgb[1];
    bout = rgb[2];
}


//sigmoid Q (cam16) and J (Jz)
void  ImProcFunctions::sigmoid_QJ(float Q, float &Qout, float middle_grey_contrast, float contrast_skewness, float middle_grey, float black_point, float white_point_disp)
{
    float film_power = 1.f;
    float white_target = white_point_disp;
    float black_target = 1.f;
    float film_fog = 1.f;
    float paper_exposure = 1.f;
    float paper_power = 1.f;
    float display_black_target = black_point;

    // compute the sigmoid parameters from the UI controls
    calculate_params(middle_grey_contrast, contrast_skewness, display_black_target,  film_power, white_target, black_target, film_fog, paper_exposure, paper_power, middle_grey);
    float value = Q;
    value = max(Q, 0.f);
    value = generalized_loglogistic_sigmoid(value, white_target, paper_exposure, film_fog, film_power, paper_power);
    Qout = value;
}

// Copyright 2018 Alberto Griggio <alberto.griggio@gmail.com>

float find_gray(float source_gray, float target_gray)
{
    // find a base such that log2lin(base, source_gray) = target_gray
    // log2lin is (base^source_gray - 1) / (base - 1), so we solve
    //
    //  (base^source_gray - 1) / (base - 1) = target_gray, that is
    //
    //  base^source_gray - 1 - base * target_gray + target_gray = 0
    //
    // use a bisection method (maybe later change to Netwon)

    if (source_gray <= 0.f) {
        return 0.f;
    }

    const auto f =
    [ = ](float x) -> float {
        return std::pow(x, source_gray) - 1.f - target_gray * x + target_gray;
    };

    // first find the interval we are interested in

    float lo = 1.f;

    while (f(lo) <= 0.f) {
        lo *= 2.f;
    }

    float hi = lo * 2.f;

    while (f(hi) >= 0.f) {
        hi *= 2.f;
    }

    if (std::isinf(hi)) {
        return 0.f;
    }

    // now search for a zero
    for (int iter = 0; iter < 100; ++iter) {
        float mid = lo + (hi - lo) / 2.f;
        float v = f(mid);

        if (std::abs(v) < 1e-4f || (hi - lo) / lo <= 1e-4f) {
            return mid;
        }

        if (v > 0.f) {
            lo = mid;
        } else {
            hi = mid;
        }
    }

    return 0.f; // not found
}

void ImProcFunctions::mean_sig(const float* const * const savenormL, float &meanf, float &stdf, int xStart, int xEnd, int yStart, int yEnd) const
{
    const int size = (yEnd - yStart) * (xEnd - xStart);
    // use double precision for large accumulations
    double meand = 0.0;
    double stdd = 0.0;
#ifdef _OPENMP
    #pragma omp parallel for reduction(+:meand, stdd) if(multiThread)
#endif

    for (int y = yStart; y < yEnd; ++y) {
        for (int x = xStart; x < xEnd; ++x) {
            meand += static_cast<double>(savenormL[y][x]);
            stdd += SQR(static_cast<double>(savenormL[y][x]));
        }
    }

    meand /= size;
    stdd /= size;
    stdd -= SQR(meand);
    stdf = std::sqrt(stdd);
    meanf = meand;
}

// taken from darktable
inline float power_norm(float r, float g, float b)
{
    r = std::abs(r);
    g = std::abs(g);
    b = std::abs(b);

    float r2 = SQR(r);
    float g2 = SQR(g);
    float b2 = SQR(b);
  
    float d = r2 + g2 + b2;
    float n = r * r2 + g * g2 + b * b2;

    return n / std::max(d, 1e-12f);
}

inline float ev2gray(float ev)
{
    return std::pow(2.f, -ev + std::log2(0.18f));
}


inline float gray2ev(float gray)
{
    return std::log2(0.18f / gray);
}

// copyright 2018 Alberto Griggio <alberto.griggio@gmail.com>

inline float norm2(float r, float g, float b, TMatrix ws)
{
    constexpr float hi = std::numeric_limits<float>::max() / 100.f;
    return std::min(hi, power_norm(r, g, b) / 2.f + Color::rgbLuminance(r, g, b, ws) / 2.f);
}

inline float norm_3(float r, float g, float b, TMatrix ws, float raplim)//lowers the equivalent luminance if the white point is high
{
    constexpr float hi = std::numeric_limits<float>::max() / 100.f;
    float pwn = 0.5f;//standard repartition between XYZ luminance and Out of gamut values 
    if (raplim < 1.2f) {//raplim : ratio between the normal value 'reasonable_limit_white_point' and reality
        pwn = 0.55f;//Tested on images with WP linear close to 4 - Near Sunset
    } else if (raplim < 1.5f) {//Very high White point
        pwn = 0.75f;//Tested on images with WP linear close to 5 or 6
    } else {
        pwn = 0.85f;//Tested on images with WP linear close to 6 and above //LEDs
    }    
    return std::min(hi, (1.f - pwn) * power_norm(r, g, b) + pwn * Color::rgbLuminance(r, g, b, ws));//I reversed the action of the two components to better account for what happens out of gamut.
}

inline float norm(float r, float g, float b, TMatrix ws)
{
    return (Color::rgbLuminance(r, g, b, ws));
}


// basic log encoding taken from ACESutil.Lin_to_Log2, from
// https://github.com/ampas/aces-dev
// (as seen on pixls.us)
// copyright 2018 Alberto Griggio <alberto.griggio@gmail.com>
void ImProcFunctions::log_encode(Imagefloat *rgb, struct local_params & lp, bool multiThread, int bfw, int bfh)
{
    // BENCHFUN
        float gray = 0.1f;
        float shadows_range = 0.f;
        bool comprlog = 0.f;
        float comprfactorlog = 0.f;
        float dynamic_range = 1.f;
        float targray = 0.1f;

    bool satcontrol = false;
        
    if(lp.logena) {
        gray = 0.01f * lp.sourcegray;
        shadows_range = lp.blackev;
        comprlog = lp.comprlo  > 0.f;
        comprfactorlog = lp.comprlo;
        dynamic_range = max(lp.whiteev - lp.blackev, 0.5f);
        targray = lp.targetgray;
        satcontrol = lp.satlog;

    } else if (lp.cieena) {
        gray = 0.01f * lp.sourcegraycie;
        shadows_range = lp.blackevjz;
        comprlog = lp.comprlocie  > 0.f;
        comprfactorlog = lp.comprlocie;
        dynamic_range = max(lp.whiteevjz - lp.blackevjz, 0.5f);
        targray = lp.targetgraycie;
        satcontrol = lp.satcie;
    }
    float comprthlog = 1.f;

    const float noise = pow_F(2.f, -16.f);
    const float log2 = xlogf(2.f);
    const float base = targray > 1 && targray < 100 && dynamic_range > 0 ? find_gray(std::abs(shadows_range) / dynamic_range, 0.01f * targray) : 0.f;
    const float linbase = rtengine::max(base, 2.f);//2 to avoid bad behavior
    TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
    float ac = -5.f;//max 4
    float bc = 4.f;
    if(comprlog < 0.6f) {
        comprthlog = ac * comprlog + bc;
    } else {
        comprthlog = 1.f;
    }

    if (settings->verbose) {
        printf("Base Log encoding std=%5.1f\n", (double) linbase);
    }

    const auto apply =
    [ = ](float x, bool scale = true) -> float {
        if (scale)
        {
            x /= 65535.f;
        }

        x = rtengine::max(x, noise);
        x = rtengine::max(x / gray, noise);
        if (comprlog && x >= comprthlog)
        {
                x = intp(comprfactorlog, (std::tanh((x - comprthlog) / comprthlog) + 1.f) * comprthlog, x);
        }
        
        
        x = rtengine::max((xlogf(x) / log2 - shadows_range) / dynamic_range, noise);
        assert(x == x);


        if (linbase > 0.f)
        {
            x = xlog2lin(x, linbase);
        }

        if (scale)
        {
            return x * 65535.f;
        } else
        {
            return x;
        }
    };

    const auto sf =
        [=](float s, float c) -> float
        {
            if (c > noise) {

                return 1.f - min(std::abs(s) / c, 1.f);
            } else {
                return 0.f;
            }
        };
//added 2024 02 
    const auto apply_sat =
        [&](float &r, float &g, float &b, float f) -> void
        {
            float ll = Color::rgbLuminance(r, g, b, ws);
            float rl = r - ll;
            float gl = g - ll;
            float bl = b - ll;
            float s = intp(max(sf(rl, r), sf(gl, g), sf(bl, b)), pow_F(f, 0.3f) * 0.6f + 0.4f, 1.f);
            r = ll + s * rl;
            g = ll + s * gl;
            b = ll + s * bl;
        };


    float detail = lp.detail;//Log encoding
    if(lp.cieena) {//Cam16
        detail = lp.detailcie;
    }
    const int W = rgb->getWidth(), H = rgb->getHeight();

    if (detail == 0.f) {//no local contrast
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int y = 0; y < H; ++y) {
            for (int x = 0; x < W; ++x) {
                float r = rgb->r(y, x);
                float g = rgb->g(y, x);
                float b = rgb->b(y, x);
                float m = norm2(r, g, b, ws);

                if (m > noise) {
                    float mm = apply(m);
                    float f = mm / m;
                    f = min(f, 1000000.f);

                    r *= f;
                    b *= f;
                    g *= f;
                    
                    if (satcontrol && f < 1.f) {
                        apply_sat(r, g, b, f);
                    }                    
                    
                    r = CLIP(r);
                    g = CLIP(g);
                    b = CLIP(b);
                }

                assert(r == r);
                assert(g == g);
                assert(b == b);

                rgb->r(y, x) = r;
                rgb->g(y, x) = g;
                rgb->b(y, x) = b;
            }
        }
    } else  {//local contrast

        array2D<float> Y(W, H);
        {
            constexpr float base_posterization = 20.f;
            array2D<float> Y2(W, H);

#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int y = 0; y < H; ++y) {
                for (int x = 0; x < W; ++x) {
                    Y2[y][x] = norm2(rgb->r(y, x), rgb->g(y, x), rgb->b(y, x), ws) / 65535.f;
                    float l = xlogf(rtengine::max(Y2[y][x], 1e-9f));
                    float ll = round(l * base_posterization) / base_posterization;
                    Y[y][x] = xexpf(ll);
                    assert(std::isfinite(Y[y][x]));
                }
            }

            const float radius = rtengine::max(rtengine::max(bfw, W), rtengine::max(bfh, H)) / 30.f;
            const float epsilon = 0.005f;
            rtengine::guidedFilter(Y2, Y, Y, radius, epsilon, multiThread);
        }
        const float blend = detail;

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int y = 0; y < H; ++y) {
            for (int x = 0; x < W; ++x) {
                float &r = rgb->r(y, x);
                float &g = rgb->g(y, x);
                float &b = rgb->b(y, x);
                float t = Y[y][x];
                float t2;

                if (t > noise && (t2 = norm2(r, g, b, ws)) > noise) {
                    float c = apply(t, false);
                    float f = c / t;
                    //   float t2 = norm(r, g, b);
                    float f2 = apply(t2) / t2;
                    f = intp(blend, f, f2);
                    f = min(f, 1000000.f);

                    //     assert(std::isfinite(f));
                    r *= f;
                    g *= f;
                    b *= f;
                    r = CLIP(r);
                    g = CLIP(g);
                    b = CLIP(b);
                    assert(std::isfinite(r));
                    assert(std::isfinite(g));
                    assert(std::isfinite(b));
                    
                    if (satcontrol && f < 1.f) {
                        apply_sat(r, g, b, f);
                    }
                    
                    
                }
            }
        }

    }
}
 
// Copyright 2018 Alberto Griggio <alberto.griggio@gmail.com>
void ImProcFunctions::getAutoLogloc(int sp, ImageSource *imgsrc, float *sourceg, float *blackev, float *whiteev, bool *blackredu,  bool *Autogr, float *sourceab,  int *whits,  int *blacks, int *whitslog,  int *blackslog, int fw, int fh, float xsta, float xend, float ysta, float yend, int SCALE)
{
    //BENCHFUN
//adpatation to local adjustments Jacques Desmis 12 2019 and 11 2021 (from ART)
// improvment white aand black toen_eqcam 9 2023 
    const PreviewProps pp(0, 0, fw, fh, SCALE);

    Imagefloat img(int(fw / SCALE + 0.5), int(fh / SCALE + 0.5));
    const ProcParams neutral;

    imgsrc->getImage(imgsrc->getWB(), TR_NONE, &img, pp, params->toneCurve, neutral.raw);
    imgsrc->convertColorSpace(&img, params->icm, imgsrc->getWB());
    float minVal = RT_INFINITY;
    float maxVal = -RT_INFINITY;
    TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);

    constexpr float noise = 1e-5;
    const int h = fh / SCALE;
    const int w = fw / SCALE;

    const int hsta = ysta * h;
    const int hend = yend * h;

    const int wsta = xsta * w;
    const int wend = xend * w;
    int www = int(fw / SCALE + 0.5);
    int hhh = int(fh / SCALE + 0.5);
    array2D<float> YY(www, hhh);
    double mean = 0.0;
    int nc = 0;     

    int whit = -whits[sp];
    int blac = -blacks[sp];

    if(params->locallab.spots.at(sp).expcie && params->locallab.spots.at(sp).Autograycie) {
        ImProcFunctions::tone_eqcam2(this, &img, whit, blac, params->icm.workingProfile, SCALE, multiThread);
    }

    int whitlog = -whitslog[sp];
    int blaclog = -blackslog[sp];
    
    if(params->locallab.spots.at(sp).explog && params->locallab.spots.at(sp).autocompute) {
        ImProcFunctions::tone_eqcam2(this, &img, whitlog, blaclog, params->icm.workingProfile, SCALE, multiThread);
    }

    for (int y = hsta; y < hend; ++y) {
        for (int x = wsta; x < wend; ++x) {
            const float r = img.r(y, x), g = img.g(y, x), b = img.b(y, x);
            YY[y][x] = norm2(r, g, b, ws) / 65535.f;//norm2 to find a best color luminance response in RGB
            mean += static_cast<double>((float) ws[1][0] * Color::gamma_srgb(r) + (float) ws[1][1] * Color::gamma_srgb(g) + (float) ws[1][2] * Color::gamma_srgb(b));
            //alternative to fing gray in case of above process does not works
            nc++;
        }
    }

    for (int y = hsta; y < hend; ++y) {
        for (int x = wsta; x < wend; ++x) {
            float l = YY[y][x];

            if (l > noise) {
                minVal = min(minVal, l);
                maxVal = max(maxVal, l);
            }
        }
    }


    if (!blackredu[sp]){//reduces white point when Freeman algo  or Sigmoid
        maxVal *= 1.5f;  
    } 
    if (!blackredu[sp]){//reduces blackpoint when Freeman algo  or Sigmoid
        minVal *= 0.5f;
    }

    //E = 2.5*2^EV => e=2.5 depends on the sensor type C=250 e=2.5 to C=330 e=3.3
    //repartition with 2.5 between 1.45 Light and shadows 0.58 => a little more 0.55...
    // https://www.pixelsham.com/2020/12/26/exposure-value-measurements/
    // https://en.wikipedia.org/wiki/Light_meter
    if (maxVal > minVal) {
        const float log2 = std::log(2.f);
        const float dynamic_range = -xlogf(minVal / maxVal) / log2;

        if (settings->verbose) {
            std::cout << "AutoLog: min = " << minVal << ", max = " << maxVal
                      << ", Dynamic Range = " << dynamic_range << std::endl;
        }

        if (Autogr[sp]) {
            double tot = 0.0;
            int n = 0;
            //0.05 0.25 arbitrary values around gray point 0.18 to find a good value as "gray" for "gain"
            const float gmax = rtengine::min(maxVal / 2.f, 0.25f);
            const float gmin = rtengine::max(minVal * std::pow(2.f, rtengine::max((dynamic_range - 1.f) / 2.f, 1.f)), 0.05f);

            if (settings->verbose) {
                std::cout << "         gray boundaries: " << gmin << ", " << gmax << std::endl;
            }

            for (int y = hsta; y < hend; ++y) {
                for (int x = wsta; x < wend; ++x) {
                    const float l = YY[y][x];

                    if (l >= gmin && l <= gmax) {
                        tot += static_cast<double>(l);
                        ++n;
                    }
                }
            }

            if (n > 0) {
                sourceg[sp] = tot / n * 100.0;

                if (settings->verbose) {
                    std::cout << "         computed gray point from " << n << " samples: " << sourceg[sp] << std::endl;
                }
            } else {//I change slightly this part of algo - more progressivity...best response in very low exposure images
                mean /= (nc * 65535.0);
                float yb;
                yb = 1.5f + 100.f * pow_F(mean, 1.8f);//empirical formula for Jz and log encode for low exposure images

                sourceg[sp] = yb;

                if (settings->verbose) {
                    std::cout << "         no samples found in range, resorting to Yb gray point value " << sourceg[sp]  << std::endl;
                }
            }
        }

        constexpr float MIN_WHITE = 2.f;
        constexpr float MAX_BLACK = -3.5f;

        const float gray = sourceg[sp] / 100.f;
        whiteev[sp] = rtengine::max(xlogf(maxVal / gray) / log2, MIN_WHITE);
        blackev[sp] = rtengine::min(whiteev[sp] - dynamic_range, MAX_BLACK);


        //calculate La - Absolute luminance shooting

        const FramesMetaData* metaData = imgsrc->getMetaData();
        float fnum = metaData->getFNumber();          // F number
        float fiso = metaData->getISOSpeed() ;        // ISO
        float fspeed = metaData->getShutterSpeed() ;  // Speed
        double fcomp = metaData->getExpComp();        // Compensation +/-
        double adap;

        if (fnum < 0.3f || fiso < 5.f || fspeed < 0.00001f) { //if no exif data or wrong
            adap = 2000.;
        } else {
            double E_V = fcomp + std::log2(double ((fnum * fnum) / fspeed / (fiso / 100.f)));
            double kexp = 0.;
            E_V += kexp * params->toneCurve.expcomp;// exposure compensation in tonecurve ==> direct EV
            E_V += 0.5 * std::log2(params->raw.expos);  // exposure raw white point ; log2 ==> linear to EV
            adap = pow(2.0, E_V - 3.0);  // cd / m2  ==> 3.0 = log2(8) =>fnum*fnum/speed = Luminance (average scene) * fiso / K (K is the reflected-light meter calibration constant according to the sensors about 12.5 or 14
            // end calculation adaptation scene luminosity
        }

        sourceab[sp] = adap;

    }
}


void tone_eq(ImProcFunctions *ipf, Imagefloat *rgb, const struct local_params &lp, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    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::tone_eqcam(ImProcFunctions *ipf, Imagefloat *rgb, int midtone, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    ToneEqualizerParams params;
    params.enabled = true;
    params.regularization = 0.f;
    params.pivot = 0.f;
    params.bands[0] = 0;
    params.bands[2] = midtone;
    params.bands[4] = 0;
    params.bands[5] = 0;
    int mid = abs(midtone);
    int threshmid = 50;
    if(mid > threshmid) {
        params.bands[1] = sign(midtone) * (mid - threshmid);
        params.bands[3] = sign(midtone) * (mid - threshmid);     
    }
    ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread);
}

void tone_eqsmooth(ImProcFunctions *ipf, Imagefloat *rgb, const struct local_params &lp, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    //smooth highlights after TRC or after GHS or log encoding Cie
    ToneEqualizerParams params;
    params.enabled = true;
    params.regularization = 0.f;
    params.pivot = 0.f;
    params.bands[0] = 0;
    params.bands[1] = 0;
    params.bands[2] = 0;
    params.bands[3] = 0;
    params.bands[4] = -30;//arbitrary value to adapt with WhiteEvjz - here White Ev # 10
    params.bands[5] = -100;//8 Ev and above
    if(lp.whiteevjz < 6) {//EV = 6 majority of images
        params.bands[4] = -15;
    }
    if(lp.smoothtrc != 0) {//RGB TRC
        params.bands[4] = -30 * lp.smoothtrc;
        params.bands[5] = -6.6f * lp.smoothtrc;
    }
    if(lp.shmeth == 2 && lp.maxdataghs > 65535.f) {//GHS maxdata
        constexpr float limit_maxdata_auto = 1.4f;//before involving the user through GHS settings
        float factor = SQR(rtengine::min(limit_maxdata_auto, lp.maxdataghs / 65535.f));//after limit_maxdata_auto user must enable Higlight attenuation, or other GHS settings
        params.bands[4] = -10 * factor;
        params.bands[5] = -50 * factor;
    }
    if(lp.islogcie || lp.issmoothghs) {//with log encoding Cie and GHS shadows Highlight
        if(!lp.issmoothghs) {
            params.bands[4] = -15;
            params.bands[5] = -50;
        } else {
            params.bands[4] = -15 -(1.f-lp.ghshp) * 120.f;//in function of HP GHS Protect highlights (HP)
            params.bands[5] = -30 -(1.f-lp.ghshp) * 100.f;
        }
        if(lp.whiteevjz < 6 && !lp.issmoothghs) {
            params.bands[4] = -10;
        }
    }
    if(lp.issmoothmich > 0.f) {//Michaelis
        params.bands[4] = - (lp.issmoothmich) * 10.f;
        params.bands[5] = - (lp.issmoothmich) * 20.f;
    }

    ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread);
}

void ImProcFunctions::tone_eqcam2(ImProcFunctions *ipf, Imagefloat *rgb, int whits, int blacks, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    ToneEqualizerParams params;
    params.enabled = true;
    params.regularization = 0.f;
    params.pivot = 0.f;
    params.bands[0] = blacks;
    int bla = abs(blacks);
    int threshblawhi = 50;
    int threshblawhi2 = 70;
    int threshblawhi3 = 40;
    if(bla > threshblawhi) {
        params.bands[1] = sign(blacks) * (bla - threshblawhi);
    }
    if(bla > threshblawhi2) {
        params.bands[2] = sign(blacks) * (bla - threshblawhi2);
    }

    params.bands[4] = whits;
    int whi = abs(whits);
    if(whi > threshblawhi) {
        params.bands[3] = sign(whits) * (whi - threshblawhi);
    }
    if(whi > threshblawhi3) {
        params.bands[5] = sign(whits) * (whi - threshblawhi3);
    }
    ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread);
}


void tone_eqblack(ImProcFunctions *ipf, Imagefloat *rgb, int blacks, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    ToneEqualizerParams params;
    params.enabled = true;
    params.regularization = 0.f;
    params.pivot = 0.f;
    params.bands[0] = blacks;
    ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread);
}


// tone mapping from
//  https://github.com/thatcherfreeman/utility-dctls/
// Copyright of the original code
/*
MIT License

Copyright (c) 2023 Thatcher Freeman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/* 
// I also took some code from Alberto Grigio
*/
//Copyright (c) 2023 Thatcher Freeman
// Adapted to Rawtherapee Jacques Desmis mars / june 2024  jdesmis@gmail.com

float ImProcFunctions::rolloff_freeman_function(float x, float dr, float b, float c, float kmid)
{
    return (dr * (x / (x + b)) + c) * kmid;//Simple sigmoid (rather a polynomial asymptotic power function) ponderate with kmid - take into account if need Mean Yb scene and Mean Yb viewing and slope value
}
//Copyright (c) 2023 Thatcher Freeman
// Adapted to Rawtherapee Jacques Desmis mars 2024  jdesmis@gmail.com
float ImProcFunctions::scene_referred_contrast(float x, float mid_gray_scene, float gamma)
{
    return mid_gray_scene * std::pow(x / mid_gray_scene, gamma);//apply gamma
}
//Copyright (c) 2023 Thatcher Freeman
// Adapted to Rawtherapee Jacques Desmis mars 2024  jdesmis@gmail.com
float ImProcFunctions::get_freeman_parameters(float x, bool rolloff_, float mid_gray_scene, float gamma, float slopelim, float dr, float b, float c, float kmid)
{
    if (rolloff_ && x <= mid_gray_scene / slopelim) {//general smooth - till Yb scene
        return x;
    } else {
        return rolloff_freeman_function(scene_referred_contrast(x, mid_gray_scene / slopelim, gamma), dr, b, c, kmid);//simulate polynomial power function with a slope to begin
    }
}

//Copyright (c) 2023 Thatcher Freeman
// Adapted to Rawtherapee Jacques Desmis - August 2024

void ImProcFunctions::tonemapFreemanQ(float Q, float &Qout, float target_slope, float white_point, float black_point, float mid_gray_scene, float mid_gray_view, bool rolloff, bool takeyb)
{
    float dr;//Dynamic Range
    float b;
    float c;//black point
    float gamma;
    float mid_gray_scene_;//Mean luminance - Scene conditions
    c = black_point;
    dr = white_point - c;
    float kmid = 1.f;
    
    if(takeyb){
        kmid = mid_gray_scene / mid_gray_view;
        kmid = cbrt(kmid);
    }
    mid_gray_scene_ = mid_gray_scene;
    
    b = (dr / (mid_gray_scene_ - c)) * (1.f - ((mid_gray_scene_ - c) / dr)) * mid_gray_scene_;//b - ponderate mid_gray_scene taking into account the total DR, and the dark part below the mid_gray_scene
    gamma = target_slope * (float) std::pow((mid_gray_scene_ + b), 2.0) / (dr * b);//Calculate gamma with slope and mid_gray_scene 
    Qout = get_freeman_parameters(Q, rolloff, mid_gray_scene_, gamma, 1.f, dr, b, c, kmid);//call main function
}



// Adapted to Rawtherapee Jacques Desmis 25 mars  - 5 june 2024
void ImProcFunctions::tonemapFreeman(float target_slope, float target_sloper, float target_slopeg , float target_slopeb, float white_point, float black_point, float mid_gray_scene, float mid_gray_view, bool rolloff, float smooththreshold, bool limslope, LUTf& lut, LUTf& lutr, LUTf& lutg, LUTf& lutb, int mode, bool scale, bool takeyb)
{
    float dr;//Dynamic Range
    float b;
    float c;//black point
    float gamma;
    float gammar;
    float gammag;
    float gammab;
    float mid_gray_scene_;//Mean luminance - Scene conditions // mid_gray_view //Mean luminance - Viewing conditions
    
    c = black_point;
    dr = white_point - c;

    if(scale) {//scale Yb mean luminance scene with white : dr and black
        mid_gray_scene_ = mid_gray_scene * dr + c;
    } else {
        mid_gray_scene_ = mid_gray_scene;
    }

    b = (dr / (mid_gray_scene_ - c)) * (1.f - ((mid_gray_scene_ - c) / dr)) * mid_gray_scene_;//b - ponderate mid_gray_scene taking into account the total DR, and the dark part below the mid_gray_scene
    gamma = target_slope * (float) std::pow((mid_gray_scene_ + b), 2.0) / (dr * b);//Caculate gamma with slope and mid_gray_scene 
    gammar = target_sloper * (float) std::pow((mid_gray_scene_ + b), 2.0) / (dr * b);//Caculate gamma with slope and mid_gray_scene 
    gammag = target_slopeg * (float) std::pow((mid_gray_scene_ + b), 2.0) / (dr * b);//Caculate gamma with slope and mid_gray_scene 
    gammab = target_slopeb * (float) std::pow((mid_gray_scene_ + b), 2.0) / (dr * b);//Caculate gamma with slope and mid_gray_scene 
    float kmid = 1.f;//general case
    //float kyb = 1.f;
    if(takeyb){
        kmid = mid_gray_scene / mid_gray_view;
        kmid = cbrt(kmid);
    }
   // if(mode == 3 && target_slope != 1.f ) {//case tone-mapping
/*
         
        float midutil = mid_gray_view / mid_gray_scene;//take into account ratio between Yb source and Yb viewing
        float midk = 1.f;
        float k_slope = 2.2f;
        if(target_slope >= 1.f) {
            midk = pow_F(midutil, k_slope * (target_slope - 1.f));//ponderation in function target_slope when "slope user" < 1.f
        }
        kmid = midk;
        
    }
*/    
    if (mode == 3 && settings->verbose) {
        printf("b=%f gamma=%f slope=%f DynRange=%f kmid=%f black=%f Yb-scale=%f\n", (double) b, (double) gamma, (double) target_slope, (double) dr, (double) kmid, (double) c, (double) mid_gray_scene_);
    }
    //lut - take from Alberto Griggio
    if(mode == 4) {
        float sloplimr = 1.f;
        float sloplimg = 1.f;
        float sloplimb = 1.f;
        if(limslope) {
            rolloff = true;
        }  
        //always apply threshold
        sloplimr *= smooththreshold;
        sloplimg *= smooththreshold;
        sloplimb *= smooththreshold;
        
        for (int i = 0; i < 65536; ++i) {// i - value image RGB
            lutr[i] = get_freeman_parameters(float(i) / 65535.f, rolloff, mid_gray_scene_, gammar, sloplimr, dr, b, c, kmid);//call main function
            lutg[i] = get_freeman_parameters(float(i) / 65535.f, rolloff, mid_gray_scene_, gammag, sloplimg, dr, b, c, kmid);//call main function
            lutb[i] = get_freeman_parameters(float(i) / 65535.f, rolloff, mid_gray_scene_, gammab, sloplimb, dr, b, c, kmid);//call main function
        }
    } else {
        kmid = 1.f;
        for (int i = 0; i < 65536; ++i) {// i - value image RGB
            lut[i] = get_freeman_parameters(float(i) / 65535.f, rolloff, mid_gray_scene_, gamma, 1.f, dr, b, c, kmid);//call main function
        }
    }
}


void ImProcFunctions::loccont(int bfw, int bfh, LabImage* tmp1, float rad, float stren, int sk)
{
    if (rad > 0.f) {
        array2D<float> guide(bfw, bfh);
        array2D<float> LL(bfw, bfh);
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16)
#endif

        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                LL[y][x] = tmp1->L[y][x];
                float ll = LL[y][x] / 32768.f;
                guide[y][x] = xlin2log(rtengine::max(ll, 0.f), 10.f);
            }
        }

        array2D<float> iL(bfw, bfh, LL, 0);
        float gu = stren * rad;
        int r = rtengine::max(int(gu / sk), 1);
        const double epsil = 0.001 * std::pow(2.f, -10);
        float st = 0.01f * rad;
        rtengine::guidedFilterLog(guide, 10.f, LL, r, epsil, false);

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16)
#endif

        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                LL[y][x] = intp(st, LL[y][x], iL[y][x]);
                tmp1->L[y][x] = LL[y][x];
            }
        }
    }
}

void ImProcFunctions::tone_eqdehaz(ImProcFunctions *ipf, Imagefloat *rgb, int whits, int blacks, const Glib::ustring &workingProfile, double scale, bool multithread)
{
    ToneEqualizerParams params;
    params.enabled = true;
    params.regularization = 0.f;
    params.pivot = 0.f;
    double blred = 0.4;
    params.bands[0] = blred * blacks;
    int bla = abs(blacks);
    int threshblawhi = 50;
    int threshblawhi2 = 85;
    if(bla > threshblawhi) {
        params.bands[1] = blred * sign(blacks) * (bla - threshblawhi);
    }
    if(bla > threshblawhi2) {
        params.bands[2] = blred * sign(blacks) * (bla - threshblawhi2);
    }

    params.bands[4] = whits;
    int whi = abs(whits);
    if(whi > threshblawhi) {
        params.bands[3] = sign(whits) * (whi - threshblawhi);
    }
    ipf->toneEqualizer(rgb, params, workingProfile, scale, multithread);
}


void sigmoidla(float &valj, float thresj, float lambda)
{
    //thres : shifts the action of sigmoid to darker tones or lights
    //lambda : changes the "slope" of the sigmoid. Low values give a flat curve, high values a "rectangular / orthogonal" curve
    valj =  1.f / (1.f + xexpf(lambda - (lambda / thresj) * valj));
}


void gamutjz(double &Jz, double &az, double &bz, double pl, const double wip[3][3], const float higherCoef, const float lowerCoef)
{
    //Not used...bad results
    constexpr float ClipLevel = 65535.0f;
    bool inGamut;

    //  int nb = 0;
    do {
        inGamut = true;
        double L_, M_, S_;
        double xx, yy, zz;
        bool zcam = false;
        Ciecam02::jzczhzxyz(xx, yy, zz, Jz, az, bz, pl, L_, M_, S_, zcam);
        double x, y, z;
        x = 65535. * (d65_d50[0][0] * xx + d65_d50[0][1] * yy + d65_d50[0][2] * zz);
        y = 65535. * (d65_d50[1][0] * xx + d65_d50[1][1] * yy + d65_d50[1][2] * zz);
        z = 65535. * (d65_d50[2][0] * xx + d65_d50[2][1] * yy + d65_d50[2][2] * zz);
        float R, G, B;
        Color:: xyz2rgb(x, y, z, R, G, B, wip);

        if (rtengine::min(R, G, B) < 0.f  || rtengine::max(R, G, B) > ClipLevel) {
            //    nb++;
            double hz = xatan2f(bz, az);
            float2 sincosval = xsincosf(hz);
            double Cz = sqrt(az * az + bz * bz);
            Cz *= (double) higherCoef;

            if (Cz < 0.01 && Jz > 0.05) { //empirical values
                Jz -= (double) lowerCoef;
            }

            az = clipazbz(Cz * (double) sincosval.y);
            bz = clipazbz(Cz * (double) sincosval.x);

            inGamut = false;
        }
    } while (!inGamut);
}

void ImProcFunctions::ciecamloc_02float(struct local_params& lp, int sp, LabImage* lab, int bfw, int bfh, int call, int sk, const LUTf& cielocalcurve, bool localcieutili, const LUTf& cielocalcurve2, bool localcieutili2,
                                        const LUTf& jzlocalcurve, bool localjzutili, const LUTf& czlocalcurve, bool localczutili, const LUTf& czjzlocalcurve, bool localczjzutili, const LocCHCurve& locchCurvejz, const LocHHCurve& lochhCurvejz, const LocLHCurve& loclhCurvejz, bool HHcurvejz, bool CHcurvejz, bool LHcurvejz,
                                        const LocwavCurve& locwavCurvejz, bool locwavutilijz, float &maxicam, float &contsig, float &lightsig
                                       )
{
//    BENCHFUN
    if (!params->locallab.spots.at(sp).activ) { //disable all ciecam functions
        return;
    }

    bool ciec = false;
    bool iscie = false;


    int modeqj = 1;
    if (params->locallab.spots.at(sp).modeQJ == "511") {
        modeqj = 0;
    } else if (params->locallab.spots.at(sp).modeQJ == "512") {
        modeqj = 1;
    }

    if (params->locallab.spots.at(sp).ciecam && params->locallab.spots.at(sp).explog && call == 1) {
        ciec = true;
        iscie = false;
    } else if (params->locallab.spots.at(sp).expcie && call == 0) {
        ciec = true;
        iscie = true;
    }

    bool z_cam = false; //params->locallab.spots.at(sp).jabcie; //alaways use normal algorithm, Zcam giev often bad results
    bool jabcie = false;//always disabled
    bool issigjz12 = params->locallab.spots.at(sp).sigjz12;
    bool issigq12 = params->locallab.spots.at(sp).sigq12;
    
    bool islogjz = params->locallab.spots.at(sp).forcebw;
    bool issigjz = params->locallab.spots.at(sp).sigjz;
    bool issigq = params->locallab.spots.at(sp).sigq;
   
    bool issig = true; //params->locallab.spots.at(sp).sigcie;

    //sigmoid J Q variables
   // const float sigmoidlambda = params->locallab.spots.at(sp).sigmoidldacie12;
   // const float sigmoidth = params->locallab.spots.at(sp).sigmoidthcie;
   // const float sigmoidbl = params->locallab.spots.at(sp).sigmoidblcie12;
    const bool sigmoidnorm = params->locallab.spots.at(sp).normcie;

    const float sigmoidlambda = params->locallab.spots.at(sp).sigmoidldacie;
    const float sigmoidth = params->locallab.spots.at(sp).sigmoidthcie;
    const float sigmoidbl = params->locallab.spots.at(sp).sigmoidblcie;


    int mobwev12 = 0;
    if (params->locallab.spots.at(sp).bwevMethod12 == "sigQ") {
        mobwev12 = 0;
    } else if (params->locallab.spots.at(sp).bwevMethod12 == "slop") {
        mobwev12 = 1;
    }

    int mobwev = 0;
    float sumcamq01 = 0.5f;

    if (params->locallab.spots.at(sp).bwevMethod == "none") {
        mobwev = 0;
    } else if (params->locallab.spots.at(sp).bwevMethod == "sig") {
        mobwev = 1;
    } else if (params->locallab.spots.at(sp).bwevMethod == "logsig") {
        mobwev = 2;
    }

    float senssig =(float) params->locallab.spots.at(sp).sigmoidsenscie;

    float middle_grey_contrast = params->locallab.spots.at(sp).sigmoidldacie12;
    float contrast_skewness = params->locallab.spots.at(sp).sigmoidthcie12;
    float white_point_disp = params->locallab.spots.at(sp).sigmoidblcie12;
    float middle_grey = 0.01 * params->locallab.spots.at(sp).sourceGraycie;
    middle_grey *= 2.f;//take into account Ciecam
    middle_grey = std::min(middle_grey, 0.6f);

    float black_point =  xexpf(lp.blackevjz * std::log(2.f) + xlogf(middle_grey));
    float white_pointsig = xexpf(lp.whiteevjz * std::log(2.f) + xlogf(middle_grey));//to adapt if need and remove slider whitsig

    float slopsmootq =(float) params->locallab.spots.at(sp).slopesmoq;
    float mid_gray_view = 0.01f * lp.targetgraycie;
    TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile);
    const double wip[3][3] = {//improve precision with double
        {wiprof[0][0], wiprof[0][1], wiprof[0][2]},
        {wiprof[1][0], wiprof[1][1], wiprof[1][2]},
        {wiprof[2][0], wiprof[2][1], wiprof[2][2]}
    };
    float plum = (float) params->locallab.spots.at(sp).pqremapcam16;

    int mocam = 1;
 
    if(lp.moka == 1) {
        mocam = 1;
    } else if (lp.moka == 2) {
        mocam = 2;
    }

    int mecamcurve = 0;

    if (params->locallab.spots.at(sp).toneMethodcie == "one") {
        mecamcurve = 0;
    } else if (params->locallab.spots.at(sp).toneMethodcie == "two") {
        mecamcurve = 1;
    }

    int mecamcurve2 = 0;

    if (params->locallab.spots.at(sp).toneMethodcie2 == "onec") {
        mecamcurve2 = 0;
    } else if (params->locallab.spots.at(sp).toneMethodcie2 == "twoc") {
        mecamcurve2 = 1;
    } else if (params->locallab.spots.at(sp).toneMethodcie2 == "thrc") {
        mecamcurve2 = 2;
    }

    float th = 1.f;
//    const float at = 1.f - sigmoidth;
//    const float bt = sigmoidth;

   // const float ath = sigmoidth - 1.f;
   // const float bth = 1;
    float sila = pow_F(sigmoidlambda, senssig);
    sila = LIM01(sila);
    const float sigm = 3.3f + 7.1f * (1.f - sila); //e^10.4 = 32860 => sigm vary from 3.3 to 10.4
    float bl = std::min(sigmoidbl, 1.f);//reused old slider
    if(params->locallab.spots.at(sp).logcieq) {
        bl = 0.01f * (float) params->locallab.spots.at(sp).strcielog;
        bl = std::min(bl, 1.f);
    }

    //end sigmoid

    int width = lab->W, height = lab->H;
    float Yw;
    Yw = 1.0f;
    double Xw, Zw;
    float f = 0.f, nc = 0.f, la, c = 0.f, xw, yw, zw, f2 = 1.f, c2 = 1.f, nc2 = 1.f, yb2;
    float fl, n, nbb, ncb, aw; //d
    float xwd, ywd, zwd, xws, yws, zws;
    //  int alg = 0;
    double Xwout, Zwout;
    double Xwsc, Zwsc;

    LUTu hist16J(32768, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true);
    LUTu hist16Q(32768, LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true);
    //for J light and contrast
    LUTf CAMBrightCurveJ(32768, LUT_CLIP_BELOW | LUT_CLIP_ABOVE);
    LUTf CAMBrightCurveQ(32768, LUT_CLIP_BELOW | LUT_CLIP_ABOVE);
    LUTf CAMBrightCurveQsig(32768, LUT_CLIP_BELOW | LUT_CLIP_ABOVE);

#ifdef _OPENMP
    const int numThreads = min(max(width * height / 65536, 1), omp_get_max_threads());
    #pragma omp parallel num_threads(numThreads) if(numThreads>1)
#endif
    {
        LUTu hist16Jthr(hist16J.getSize(), LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true);
        LUTu hist16Qthr(hist16Q.getSize(), LUT_CLIP_BELOW | LUT_CLIP_ABOVE, true);

#ifdef _OPENMP
        #pragma omp for
#endif

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) { //rough correspondence between L and J
                float currL = lab->L[i][j] / 327.68f;
                float koef; //rough correspondence between L and J

                if (currL > 50.f) {
                    if (currL > 70.f) {
                        if (currL > 80.f) {
                            if (currL > 85.f) {
                                koef = 0.97f;
                            } else {
                                koef = 0.93f;
                            }
                        } else {
                            koef = 0.87f;
                        }
                    } else {
                        if (currL > 60.f) {
                            koef = 0.85f;
                        } else {
                            koef = 0.8f;
                        }
                    }
                } else {
                    if (currL > 10.f) {
                        if (currL > 20.f) {
                            if (currL > 40.f) {
                                koef = 0.75f;
                            } else {
                                koef = 0.7f;
                            }
                        } else {
                            koef = 0.9f;
                        }
                    } else {
                        koef = 1.0;
                    }
                }

                hist16Jthr[(int)((koef * lab->L[i][j]))]++;    //evaluate histogram luminance L # J
                hist16Qthr[CLIP((int)(32768.f * sqrt((koef * (lab->L[i][j])) / 32768.f)))]++;     //for brightness Q : approximation for Q=wh*sqrt(J/100)  J not equal L
            }
        }

#ifdef _OPENMP
        #pragma omp critical
#endif
        {
            hist16J += hist16Jthr;
            hist16Q += hist16Qthr;
        }
    }
#ifdef _OPENMP
    static_cast<void>(numThreads); // to silence cppcheck warning
#endif

    //evaluate lightness, contrast

    if (ciec) {
        float contL = 0.f;
        float lightL = 0.f;
        float contQ = 0.f;
        float lightQ = 0.f;

        if (iscie) {
            contL = 0.6 * params->locallab.spots.at(sp).contlcie; //0.6 less effect, no need 1.
            lightL = 0.4 * params->locallab.spots.at(sp).lightlcie; //0.4 less effect, no need 1.
            contQ = 0.5 * params->locallab.spots.at(sp).contqcie; //0.5 less effect, no need 1.
            lightQ = 0.4 * params->locallab.spots.at(sp).lightqcie; //0.4 less effect, no need 1.
        } else {
            contL = 0.6 * params->locallab.spots.at(sp).contl; //0.6 less effect, no need 1.
            lightL = 0.4 * params->locallab.spots.at(sp).lightl; //0.4 less effect, no need 1.
            contQ = 0.5 * params->locallab.spots.at(sp).contq; //0.5 less effect, no need 1.
            lightQ = 0.4 * params->locallab.spots.at(sp).lightq; //0.4 less effect, no need 1.

        }

        float contthresL = 0.f;

        if (iscie) {
            contthresL = params->locallab.spots.at(sp).contthrescie;
        } else {
            contthresL = params->locallab.spots.at(sp).contthres;
        }

        float contthresQ = contthresL;

        if (contL < 0.f) {
            contthresL *= -1;
        }

        float thL = 0.6f;
        thL = 0.3f * contthresL + 0.6f;

        if (contQ < 0.f) {
            contthresQ *= -1;
        }

        float thQ = 0.6f;
        thQ = 0.3f * contthresQ + 0.6f;
        float thQsig = 0.6f;
        Ciecam02::curveJfloat(lightL, contL, thL, hist16J, CAMBrightCurveJ); //lightness J and contrast J
        CAMBrightCurveJ /= 327.68f;
        double podcont = 40.;//50
        double podcont0 = 40.;//50
        double podcont1 = 30.;//35.
        double ka = -(podcont0 - podcont1) / 0.5;
        double kb = podcont1 - ka;

        double podlight = 35.;
        double podlight0 = 35.;
        double podlight1 = 40.;//45
        double kal = -(podlight0 - podlight1) / 0.5;
        double kbl = podlight1 - kal;
        
        double contbase = params->locallab.spots.at(sp).sigmoidldacie;
        
        if(contbase <= 0.5)  {
            podcont = podcont0;
            podlight = podlight0;
            
        } else {
            podcont = ka * contbase + kb;
            podlight = kal * contbase + kbl;           
        }
        Ciecam02::curveJfloat(lightQ, contQ, thQ, hist16Q, CAMBrightCurveQ); //brightness Q and contrast Q
        lightsig = -podlight * contbase;
        contsig = podcont * contbase;
        Ciecam02::curveJfloat(lightsig, contsig, thQsig, hist16Q, CAMBrightCurveQsig); //brightness Q and contrast Q bypass.
    }


    int tempo = 5000;

    if (params->locallab.spots.at(sp).expvibrance && call == 2) {
        if (params->locallab.spots.at(sp).warm > 0) {
            tempo = 5000 - 30 * params->locallab.spots.at(sp).warm;
        } else if (params->locallab.spots.at(sp).warm < 0) {
            tempo = 5000 - 70 * params->locallab.spots.at(sp).warm;
        }
    }


    if (ciec) {
        if (iscie) {
            if (params->locallab.spots.at(sp).catadcie > 0) {
                tempo = 5000 - 30 * params->locallab.spots.at(sp).catadcie;
            } else if (params->locallab.spots.at(sp).catadcie < 0) {
                tempo = 5000 - 70 * params->locallab.spots.at(sp).catadcie;
            }
        } else {
            if (params->locallab.spots.at(sp).catad > 0) {
                tempo = 5000 - 30 * params->locallab.spots.at(sp).catad;
            } else if (params->locallab.spots.at(sp).catad < 0) {
                tempo = 5000 - 70 * params->locallab.spots.at(sp).catad;
            }
        }
    }

    ColorTemp::temp2mulxyz(params->wb.temperature, params->wb.method, params->wb.observer, Xw, Zw);  //compute white Xw Yw Zw  : white current WB
    ColorTemp::temp2mulxyz(tempo, "Custom", params->wb.observer, Xwout, Zwout);
    ColorTemp::temp2mulxyz(5000, "Custom", params->wb.observer, Xwsc, Zwsc);

    //viewing condition for surrsrc
    f  = 1.00f;
    c  = 0.69f;
    nc = 1.00f;
    //viewing condition for surround
    f2 = 1.0f, c2 = 0.69f, nc2 = 1.0f;

    if (ciec) {
        if (iscie) {
            //surround source with only 2 choices (because Log encoding before)
            if(lp.sursouci == 0) {
                f = 1.0f, c = 0.69f, nc = 1.0f;
            } else if (lp.sursouci == 1){
                f  = 0.9f;
                c  = 0.59f;
                nc = 0.9f;
            } else if (lp.sursouci == 2) {
                f  = 0.8f;
                c  = 0.525f;
                nc = 0.8f;
            } else if (lp.sursouci == 3) {
                f  = 0.8f;
                c  = 0.41f;
                nc = 0.8f;
            } else if (lp.sursouci == 4) {
                f = 1.0f, c = 0.702f, nc = 1.0f;//very small surround effect for Jz - Also disable Ciecam further
            }
        } else {
            if (params->locallab.spots.at(sp).sursour == "Average") {
                f = 1.0f, c = 0.69f, nc = 1.0f;
            } else if (params->locallab.spots.at(sp).sursour == "Dim") {
                f  = 0.9f;
                c  = 0.59f;
                nc = 0.9f;
            } else if (params->locallab.spots.at(sp).sursour == "Dark") {
                f  = 0.8f;
                c  = 0.525f;
                nc = 0.8f;
            } else if (params->locallab.spots.at(sp).sursour == "exDark") {
                f  = 0.8f;
                c  = 0.41f;
                nc = 0.8f;
            }
        }

        //viewing condition for surround
        if (iscie) {
            if (params->locallab.spots.at(sp).surroundcie == "Average") {
                f2 = 1.0f, c2 = 0.69f, nc2 = 1.0f;
            } else if (params->locallab.spots.at(sp).surroundcie == "Dim") {
                f2  = 0.9f;
                c2  = 0.59f;
                nc2 = 0.9f;
            } else if (params->locallab.spots.at(sp).surroundcie == "Dark") {
                f2  = 0.8f;
                c2  = 0.525f;
                nc2 = 0.8f;
            } else if (params->locallab.spots.at(sp).surroundcie == "ExtremelyDark") {
                f2  = 0.8f;
                c2  = 0.41f;
                nc2 = 0.8f;
            }
        } else {
            if (params->locallab.spots.at(sp).surround == "Average") {
                f2 = 1.0f, c2 = 0.69f, nc2 = 1.0f;
            } else if (params->locallab.spots.at(sp).surround == "Dim") {
                f2  = 0.9f;
                c2  = 0.59f;
                nc2 = 0.9f;
            } else if (params->locallab.spots.at(sp).surround == "Dark") {
                f2  = 0.8f;
                c2  = 0.525f;
                nc2 = 0.8f;
            } else if (params->locallab.spots.at(sp).surround == "ExtremelyDark") {
                f2  = 0.8f;
                c2  = 0.41f;
                nc2 = 0.8f;
            }

        }
    }

    xwd = 100.0 * Xwout;
    zwd = 100.0 * Zwout;
    ywd = 100.f;

    xws = 100.0 * Xwsc;
    zws = 100.0 * Zwsc;
    yws = 100.f;


    //La and la2 = ambiant luminosity scene and viewing
    la = 400.f;
    float la2 = 400.f;

    if (ciec) {
        if (iscie) {
            la = params->locallab.spots.at(sp).sourceabscie;
            la2 = params->locallab.spots.at(sp).targabscie;
        } else {
            la = params->locallab.spots.at(sp).sourceabs;
            la2 = params->locallab.spots.at(sp).targabs;
        }
    }

    const float pilot = 2.f;
    const float pilotout = 2.f;
    double avgm = 0.;
    //algoritm's params
    float yb = 18.f;
    yb2 = 18;

    if (ciec) {
        if (iscie) {
            yb = params->locallab.spots.at(sp).sourceGraycie;//
            avgm = (double) pow_F(0.01f * (yb - 1.f), 0.45f);;
            yb2 = params->locallab.spots.at(sp).targetGraycie;
        } else {
            yb = params->locallab.spots.at(sp).targetGray;//target because we are after Log encoding
            yb2 = params->locallab.spots.at(sp).targetGray;
        }
    }

    if (params->locallab.spots.at(sp).expcie && call == 10 && params->locallab.spots.at(sp).modecam == "jz") {
        yb = params->locallab.spots.at(sp).sourceGraycie;//for Jz calculate Yb and surround in Lab and cam16 before process Jz
        la = params->locallab.spots.at(sp).sourceabscie;
            if(lp.sursouci == 0) {
                f = 1.0f, c = 0.69f, nc = 1.0f;
            } else if (lp.sursouci == 1){
                f  = 0.9f;
                c  = 0.59f;
                nc = 0.9f;
            } else if (lp.sursouci == 2) {
                f  = 0.8f;
                c  = 0.525f;
                nc = 0.8f;
            } else if (lp.sursouci == 3) {
                f  = 0.8f;
                c  = 0.41f;
                nc = 0.8f;
            } else if (lp.sursouci == 4) {
                f = 1.0f, c = 0.702f, nc = 1.0f;//very small surround effect for Jz
            }
       
    }

    float schr = 0.f;
    float mchr = 0.f;
    float cchr = 0.f;
    float rstprotection = 0.f;
    float hue = 0.f;

    if (ciec) {
        if (iscie) {
            rstprotection =  params->locallab.spots.at(sp).rstprotectcie;
            hue = params->locallab.spots.at(sp).huecie;

            cchr = params->locallab.spots.at(sp).chromlcie;

            if (cchr == -100.0f) {
                cchr = -99.8f;
            }

            schr = params->locallab.spots.at(sp).saturlcie;

            if (schr > 0.f) {
                schr = schr / 2.f;    //divide sensibility for saturation
            }

            if (schr == -100.f) {
                schr = -99.8f;
            }

            mchr = params->locallab.spots.at(sp).colorflcie;

            if (mchr == -100.0f) {
                mchr = -99.8f ;
            }

            if (mchr == 100.0f) {
                mchr = 99.9f;
            }

        } else {
            cchr = params->locallab.spots.at(sp).chroml;

            if (cchr == -100.0f) {
                cchr = -99.8f;
            }

            schr = params->locallab.spots.at(sp).saturl;

            if (schr > 0.f) {
                schr = schr / 2.f;    //divide sensibility for saturation
            }

            if (schr == -100.f) {
                schr = -99.8f;
            }

            mchr = params->locallab.spots.at(sp).colorfl;

            if (mchr == -100.0f) {
                mchr = -99.8f ;
            }

            if (mchr == 100.0f) {
                mchr = 99.9f;
            }
        }
    }

    float d, dj;

    // const int gamu = 0; //(params->colorappearance.gamut) ? 1 : 0;
    xw = 100.0 * Xw;
    yw = 100.f * Yw;
    zw = 100.0 * Zw;
    float xw1 = xws, yw1 = yws, zw1 = zws, xw2 = xwd, yw2 = ywd, zw2 = zwd;
    float cz, wh, pfl;
    int c16 = 16;//always cat16
    bool c20 = true;

    if (c20  && plum > 100.f) {
        c16 = 21;//I define 21...for 2021 :)
    }

    int level_bljz = params->locallab.spots.at(sp).csthresholdjz.getBottomLeft();
    int level_hljz = params->locallab.spots.at(sp).csthresholdjz.getTopLeft();
    int level_brjz = params->locallab.spots.at(sp).csthresholdjz.getBottomRight();
    int level_hrjz = params->locallab.spots.at(sp).csthresholdjz.getTopRight();

    float alowjz = 1.f;
    float blowjz = 0.f;

    if (level_hljz != level_bljz) {
        alowjz = 1.f / (level_hljz - level_bljz);
        blowjz = -alowjz * level_bljz;
    }

    float ahighjz = 1.f;
    float bhighjz = 0.f;

    if (level_hrjz != level_brjz) {
        ahighjz = 1.f / (level_hrjz - level_brjz);
        bhighjz =  -ahighjz * level_brjz;
    }

    float sigmalcjz = params->locallab.spots.at(sp).sigmalcjz;
    float jzamountchr = 0.01 * params->locallab.spots.at(sp).thrhjzcie;
    bool jzch = params->locallab.spots.at(sp).chjzcie;
    double jzamountchroma = 0.01 * settings->amchromajz;

    if (jzamountchroma < 0.05) {
        jzamountchroma = 0.05;
    }

    if (jzamountchroma > 2.) {
        jzamountchroma = 2.;
    }

    Ciecam02::initcam1float(yb, pilot, f, la, xw, yw, zw, n, d, nbb, ncb, cz, aw, wh, pfl, fl, c, c16, plum);
    const float pow1 = pow_F(1.64f - pow_F(0.29f, n), 0.73f);
    float nj, nbbj, ncbj, czj, awj, flj;
    Ciecam02::initcam2float(yb2, pilotout, f2,  la2,  xw2,  yw2,  zw2, nj, dj, nbbj, ncbj, czj, awj, flj, c16, plum);
#if defined(__SSE2__) || defined(RT_SIMDE)
    const float reccmcz = 1.f / (c2 * czj);
#endif
    const float epsil = 0.0001f;
    const float coefQ = 32767.f / wh;
    const float coefq = 1.f / wh;

    const float pow1n = pow_F(1.64f - pow_F(0.29f, nj), 0.73f);
    const float coe = pow_F(fl, 0.25f);
    const float QproFactor = (0.4f / c) * (aw + 4.0f) ;
    const double shadows_range =  params->locallab.spots.at(sp).blackEvjz;
    const double targetgray = params->locallab.spots.at(sp).targetjz;
    double targetgraycor = 0.15;
    double dynamic_range = std::max(params->locallab.spots.at(sp).whiteEvjz - shadows_range, 0.5);
    const double noise = pow(2., -16.6);//16.6 instead of 16 a little less than others, but we work in double
    const double log2 = xlog(2.);
    const float log2f = xlogf(2.f);
    
    float middle_grey_contrastjz = params->locallab.spots.at(sp).sigmoidldajzcie12;
    float contrast_skewnessjz = params->locallab.spots.at(sp).sigmoidthjzcie12;
    float white_point_dispjz = params->locallab.spots.at(sp).sigmoidbljzcie12;
    float middle_greyjz = 0.01 * params->locallab.spots.at(sp).sourceGraycie;
    middle_greyjz *= 2.f;
    middle_greyjz = std::min(middle_greyjz, 0.6f);

    float black_pointjz =  xexpf(lp.blackevjz * std::log(2.f) + xlogf(middle_greyjz));
    float white_pointsigjz = xexpf(lp.whiteevjz * std::log(2.f) + xlogf(middle_greyjz));//to adapt if need and remove slider whitsig
    float drjz = white_pointsigjz - black_pointjz;
    if(params->locallab.spots.at(sp).sigybjz12) {
        middle_greyjz = middle_greyjz * drjz + black_pointjz;
    }

    if ((mocam == 2)  && call == 0) { //Jz az bz ==> Jz Cz Hz before Ciecam16
        double mini = 1000.;
        double maxi = -1000.;
        double sum = 0.;
        int nc = 0;
        double epsiljz = 0.0001;
        //Remapping see https://hal.inria.fr/hal-02131890/document    I took some ideas in this text, and add my personal adaptation
        // image quality assessment of HDR and WCG images https://tel.archives-ouvertes.fr/tel-02378332/document
        double adapjz = params->locallab.spots.at(sp).adapjzcie;
        double jz100 = params->locallab.spots.at(sp).jz100;
        double pl = params->locallab.spots.at(sp).pqremap;
        double jzw, azw, bzw;
        jzw = 0.18;//Jz white

        bool Qtoj = params->locallab.spots.at(sp).qtoj;//betwwen lightness to brightness
        const bool logjz =  params->locallab.spots.at(sp).logjz;//log encoding
//calculate min, max, mean for Jz
#ifdef _OPENMP
        #pragma omp parallel for reduction(min:mini) reduction(max:maxi) reduction(+:sum) if(multiThread)
#endif

        for (int i = 0; i < height; i += 1) {
            for (int k = 0; k < width; k += 1) {
                float L = lab->L[i][k];
                float a = lab->a[i][k];
                float b = lab->b[i][k];
                float x, y, z;
                //convert Lab => XYZ
                Color::Lab2XYZ(L, a, b, x, y, z);
                x = x / 65535.f;
                y = y / 65535.f;
                z = z / 65535.f;
                double Jz, az, bz;
                double xx, yy, zz;
                //D50 ==> D65
                xx = (d50_d65[0][0] * (double) x + d50_d65[0][1] * (double) y + d50_d65[0][2] * (double) z);
                yy = (d50_d65[1][0] * (double) x + d50_d65[1][1] * (double) y + d50_d65[1][2] * (double) z);
                zz = (d50_d65[2][0] * (double) x + d50_d65[2][1] * (double) y + d50_d65[2][2] * (double) z);

                double L_p, M_p, S_p;
                bool zcam = z_cam;

                Ciecam02::xyz2jzczhz(Jz, az, bz, xx, yy, zz, pl, L_p, M_p, S_p, zcam);

                if (Jz > maxi) {
                    maxi = Jz;
                }

                if (Jz < mini) {
                    mini = Jz;
                }

                sum += Jz;
                // I read bz, az values and Hz ==> with low chroma values Hz are very different from lab always around 1.4 radians ???? for blue...
            }
        }

        nc = height * width;
        sum = sum / nc;
        maxi += epsiljz;
        sum += epsiljz;
        //remapping Jz
        double ijz100 = 1. / jz100;
        double ajz = (ijz100 - 1.) / 9.; //9 = sqrt(100) - 1 with a parabolic curve after jz100 - we can change for others curve ..log...(you must change also in locallabtool2)
        double bjz = 1. - ajz;
        //relation between adapjz and Absolute luminance source (La), adapjz =sqrt(La) - see locallabtool2 adapjzcie
        double interm = jz100 * (adapjz * ajz + bjz);
        double bj = (10. - maxi) / 9.;
        double aj = maxi - bj;
        double to_screen = (aj * interm + bj) / maxi;
        //to screen - remapping of Jz in function real scene absolute luminance

        double to_one = 1.;//only for calculation in range 0..1 or 0..32768
        to_one = 1 / (maxi * to_screen);

        if (adapjz == 10.) { //force original algorithm if La > 10000
            to_screen = 1.;
        }

        if (Qtoj) {
            double xxw = (d50_d65[0][0] * (double) Xw + d50_d65[0][1] * (double) Yw + d50_d65[0][2] * (double) Zw);
            double yyw = (d50_d65[1][0] * (double) Xw + d50_d65[1][1] * (double) Yw + d50_d65[1][2] * (double) Zw);
            double zzw = (d50_d65[2][0] * (double) Xw + d50_d65[2][1] * (double) Yw + d50_d65[2][2] * (double) Zw);
            double L_pa, M_pa, S_pa;
            Ciecam02::xyz2jzczhz(jzw, azw, bzw, xxw, yyw, zzw, pl, L_pa, M_pa, S_pa, z_cam);

            if (settings->verbose) { //calculate Jz white for use of lightness instead brightness
                printf("Jzwhite=%f \n", jzw);
            }

        }

        const std::unique_ptr<LabImage> temp(new LabImage(width, height));
        const std::unique_ptr<LabImage> tempresid(new LabImage(width, height));
        const std::unique_ptr<LabImage> tempres(new LabImage(width, height));
        array2D<double> JJz(width, height);
        array2D<double> Aaz(width, height);
        array2D<double> Bbz(width, height);
        int highhs =  params->locallab.spots.at(sp).hljzcie;
        int hltonahs = params->locallab.spots.at(sp).hlthjzcie;
        int shadhs = params->locallab.spots.at(sp).shjzcie;
        int shtonals = params->locallab.spots.at(sp).shthjzcie;
        int radhs = params->locallab.spots.at(sp).radjzcie;
        float softjz = (float) params->locallab.spots.at(sp).softjzcie;

        avgm = 0.5 * (sum * to_screen * to_one + avgm);//empirical formula
        double miny = 0.1;
        double delta = 0.015 * (double) sqrt(std::max(100.f, la) / 100.f);//small adaptation in function La scene
        double maxy = 0.65;//empirical value
        double maxreal = maxi * to_screen;
        double maxjzw = jzw * to_screen;

        if (settings->verbose) {
            printf("La=%4.1f PU_adap=%2.1f maxi=%f mini=%f mean=%f, avgm=%f to_screen=%f Max_real=%f to_one=%f\n", (double) la, adapjz, maxi, mini, sum, avgm, to_screen, maxreal, to_one);
        }
        const float sigmoidlambdajz = params->locallab.spots.at(sp).sigmoidldajzcie;
        const float sigmoidthjz = params->locallab.spots.at(sp).sigmoidthjzcie;
        const float sigmoidbljz = params->locallab.spots.at(sp).sigmoidbljzcie;

        float thjz = 1.f;
        const float atjz = 1.f - sigmoidthjz;
        const float btjz = sigmoidthjz;

        const float athjz = sigmoidthjz - 1.f;
        const float bthjz = 1.f;
        float powsig = pow_F(sigmoidlambdajz, 0.5f);
        const float sigmjz = 3.3f + 7.1f * (1.f - powsig); // e^10.4 = 32860
        const float bljz = sigmoidbljz;



        double contreal = 0.2 *  params->locallab.spots.at(sp).contjzcie;
        DiagonalCurve jz_contrast({
            DCT_NURBS,
            0, 0,
            avgm - avgm * (0.6 - contreal / 250.0), avgm - avgm * (0.6 + contreal / 250.0),
            avgm + (1. - avgm) * (0.6 - contreal / 250.0), avgm + (1. - avgm) * (0.6 + contreal / 250.0),
            1, 1
        });
        //all calculations in double for best results...but slow
        double lightreal = 0.2 *  params->locallab.spots.at(sp).lightjzcie;
        double chromz =  params->locallab.spots.at(sp).chromjzcie;
        double saturz =  params->locallab.spots.at(sp).saturjzcie;
        double dhue = 0.0174 * params->locallab.spots.at(sp).huejzcie;
        DiagonalCurve jz_light({
            DCT_NURBS,
            0, 0,
            miny, miny + lightreal / 150.,
            maxy, min(1.0, maxy + delta + lightreal / 300.0),
            1, 1
        });
        DiagonalCurve jz_lightn({
            DCT_NURBS,
            0, 0,
            max(0.0, miny  - lightreal / 150.), miny,
            maxy + delta - lightreal / 300.0, maxy + delta,
            1, 1
        });
        bool wavcurvejz = false;

        if (locwavCurvejz && locwavutilijz) {
            for (int i = 0; i < 500; i++) {
                if (locwavCurvejz[i] != 0.5f) {
                    wavcurvejz = true;
                    break;
                }
            }
        }

        float mjjz = lp.mLjz;

        if (wavcurvejz && lp.mLjz == 0.f) {
            mjjz = 0.0f;//to enable clarity if need in some cases mjjz = 0.0001f
        }

        //log encoding Jz
        double gray = 0.15;
        /*
        const double shadows_range =  params->locallab.spots.at(sp).blackEvjz;
        const double targetgray = params->locallab.spots.at(sp).targetjz;
        double targetgraycor = 0.15;
        double dynamic_range = std::max(params->locallab.spots.at(sp).whiteEvjz - shadows_range, 0.5);
        const double noise = pow(2., -16.6);//16.6 instead of 16 a little less than others, but we work in double
        const double log2 = xlog(2.);
        */
        double base = 10.;
        double linbase = 10.;

        if (logjz) { //with brightness Jz
            gray = 0.01 * params->locallab.spots.at(sp).sourceGraycie;//acts as amplifier (gain) : needs same type of modifications than targetgraycor with pow
            gray = pow(gray, 1.2);//or 1.15 => modification to increase sensitivity gain, only on defaults, of course we can change this value manually...take into account suuround and Yb Cam16
            targetgraycor = pow(0.01 * targetgray, 1.15);//or 1.2 small reduce effect -> take into account a part of surround (before it was at 1.2)
            base = targetgray > 1. && targetgray < 100. && dynamic_range > 0. ? (double) find_gray(std::abs((float) shadows_range) / (float) dynamic_range, (float)(targetgraycor)) : 0.;
            linbase = std::max(base, 2.);//2. minimal base log to avoid very bad results

            if (settings->verbose) {
                printf("Base logarithm encoding Jz=%5.1f\n", linbase);
            }
        }

        const auto applytojz =
        [ = ](double x) -> double {

            x = std::max(x, noise);
            x = std::max(x / gray, noise);//gray = gain - before log conversion
            x = std::max((xlog(x) / log2 - shadows_range) / dynamic_range, noise);//x in range EV
            assert(x == x);

            if (linbase > 0.)//apply log base in function of targetgray blackEvjz and Dynamic Range
            {
                x = xlog2lin(x, linbase);
            }

            return x;
        };

#ifdef _OPENMP
        #pragma omp parallel for if(multiThread)
#endif

        for (int i = 0; i < height; i++) {
            for (int k = 0; k < width; k++) {
                float L = lab->L[i][k];
                float a = lab->a[i][k];
                float b = lab->b[i][k];
                float x, y, z;
                //convert Lab => XYZ
                Color::Lab2XYZ(L, a, b, x, y, z);
                x = x / 65535.f;
                y = y / 65535.f;
                z = z / 65535.f;
                double Jz, az, bz;//double need because matrix with const(1.6295499532821566e-11) and others
                double xx, yy, zz;
                //change WP to D65
                xx = (d50_d65[0][0] * (double) x + d50_d65[0][1] * (double) y + d50_d65[0][2] * (double) z);
                yy = (d50_d65[1][0] * (double) x + d50_d65[1][1] * (double) y + d50_d65[1][2] * (double) z);
                zz = (d50_d65[2][0] * (double) x + d50_d65[2][1] * (double) y + d50_d65[2][2] * (double) z);

                double L_p, M_p, S_p;
                bool zcam = z_cam;
                Ciecam02::xyz2jzczhz(Jz, az, bz, xx, yy, zz, pl, L_p, M_p, S_p, zcam);
                //remapping Jz
                Jz = Jz * to_screen;
                az = az * to_screen;
                bz = bz * to_screen;
                JJz[i][k] = Jz;
                Aaz[i][k] = az;
                Bbz[i][k] = bz;

                if (highhs > 0 || shadhs > 0  || wavcurvejz || mjjz != 0.f || lp.mCjz != 0.f  || LHcurvejz || HHcurvejz || CHcurvejz) {
                    //here we work in float with usual functions  SH / wavelets / curves H
                    temp->L[i][k] = tempresid->L[i][k] = tempres->L[i][k] = (float) to_one * 32768.f * (float) JJz[i][k];
                    temp->a[i][k] = tempresid->a[i][k] = tempres->a[i][k] = (float) to_one * 32768.f * (float) Aaz[i][k];
                    temp->b[i][k] = tempresid->b[i][k] = tempres->b[i][k] = (float) to_one * 32768.f * (float) Bbz[i][k];
                }
            }
        }

        if (highhs > 0 || shadhs > 0) {
            ImProcFunctions::shadowsHighlights(temp.get(), true, 1, highhs, shadhs, radhs, sk, hltonahs * maxi * to_screen * to_one, shtonals * maxi * to_screen * to_one);
#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int i = 0; i < height; i++) {
                for (int k = 0; k < width; k++) {//reinitialize datas after SH...: guide, etc.
                    tempresid->L[i][k] = tempres->L[i][k] = temp->L[i][k];
                    tempresid->a[i][k] = tempres->a[i][k] = temp->a[i][k];
                    tempresid->b[i][k] = tempres->b[i][k] = temp->b[i][k];
                }
            }
        }

        //others "Lab" treatment...to adapt

        if (wavcurvejz  || mjjz != 0.f || lp.mCjz != 0.f) { //local contrast wavelet and clarity
#ifdef _OPENMP
            const int numThreads = omp_get_max_threads();
#else
            const int numThreads = 1;

#endif
            // adap maximum level wavelet to size of RT-spot
            int wavelet_level = 1 + params->locallab.spots.at(sp).csthresholdjz.getBottomRight();//retrieve with +1 maximum wavelet_level
            wavelet_level = rtengine::max(5, wavelet_level);
            int minwin = rtengine::min(width, height);
            int maxlevelspot = 9;//maximum possible

            // adapt maximum level wavelet to size of crop
            while ((1 << maxlevelspot) >= (minwin) && maxlevelspot  > 1) {
                --maxlevelspot ;
            }


            wavelet_level = rtengine::min(wavelet_level, maxlevelspot);
            int maxlvl = wavelet_level;

            //simple local contrast in function luminance
            if (locwavCurvejz && locwavutilijz && wavcurvejz) {
                float strengthjz = 1.2f;
                std::unique_ptr<wavelet_decomposition> wdspot(new wavelet_decomposition(temp->L[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));//lp.daubLen

                if (wdspot->memory_allocation_failed()) {
                    return;
                }

                maxlvl = wdspot->maxlevel();
                wavlc(*wdspot, level_bljz, level_hljz, maxlvl, level_hrjz, level_brjz, ahighjz, bhighjz, alowjz, blowjz, sigmalcjz, 1.f, strengthjz, locwavCurvejz, numThreads);
                wdspot->reconstruct(temp->L[0], 1.f);

            }

            float thr = 0.001f;
            int flag = 2;

            // begin clarity wavelet jz
            if (mjjz != 0.f || lp.mCjz != 0.f) {
                float mL0 = 0.f;
                float mC0 = 0.f;
                bool exec = false;
                float mL = mjjz;
                float mC = lp.mCjz;
                clarimerge(lp, mL, mC, exec, tempresid.get(), wavelet_level, sk, numThreads);

                if (maxlvl <= 4) {
                    mL0 = 0.f;
                    mC0 = 0.f;
                    mL = -1.5f * mL;//increase only for sharpen
                    mC = -mC;
                    thr = 1.f;
                    flag = 0;

                } else {
                    mL0 = mL;
                    mC0 = mC;
                    thr = 1.f;
                    flag = 2;
                }

                LabImage *mergfile = temp.get();
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int x = 0; x < height; x++)
                    for (int y = 0; y < width; y++) {
                        temp->L[x][y] = clipLoc((1.f + mL0) * mergfile->L[x][y] - mL * tempresid->L[x][y]);
                        temp->a[x][y] = clipC((1.f + mC0) * mergfile->a[x][y] - mC * tempresid->a[x][y]);
                        temp->b[x][y] = clipC((1.f + mC0) * mergfile->b[x][y] - mC * tempresid->b[x][y]);
                    }
            }

            if (lp.softrjz >= 0.5f && (wavcurvejz || std::fabs(mjjz) > 0.001f)) {//guidedfilter
                softproc(tempres.get(), temp.get(), lp.softrjz, height, width, 0.001, 0.00001, thr, sk, multiThread, flag);
            }
        }

//new curves Hz
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < height; i++) {
            for (int k = 0; k < width; k++) {
                float j_z = temp->L[i][k];
                float C_z = sqrt(SQR(temp->a[i][k]) + SQR(temp->b[i][k]));
                float c_z = C_z / 32768.f;

                if (loclhCurvejz && LHcurvejz) {//Jz=f(Hz) curve
                    float kcz = (float) jzamountchr;
                    float Hz = xatan2f(temp->b[i][k], temp->a[i][k]);
                    float l_r = j_z / 32768.f;
                    float kcc = SQR(c_z / kcz);
                    jzch = true;

                    if (jzch == false) {
                        kcc = 1.f;
                    } else if (kcc > 1.f) {
                        kcc = 1.f; //cbrt(kcc);
                    }

                    float valparam = loclhCurvejz[500.f * static_cast<float>(Color::huejz_to_huehsv2((float) Hz))] - 0.5f;

                    float valparamneg;
                    valparamneg = valparam;
                    valparam *= 2.f * kcc;
                    valparamneg *= kcc;

                    if (valparam > 0.f) {
                        l_r = (1.f - valparam) * l_r + valparam * (1.f - SQR(((SQR(1.f - min(l_r, 1.0f))))));
                    } else
                        //for negative
                    {
                        float khue = 1.9f; //in reserve in case of!
                        l_r *= (1.f + khue * valparamneg);
                    }

                    temp->L[i][k] = l_r * 32768.f;
                }

                if (locchCurvejz && CHcurvejz) {//Cz=f(Hz) curve
                    float Hz = xatan2f(temp->b[i][k], temp->a[i][k]);
                    const float valparam = 1.5f * (locchCurvejz[500.f * static_cast<float>(Color::huejz_to_huehsv2((float)Hz))] - 0.5f);  //get valp=f(H)
                    float chromaCzfactor = 1.0f + valparam;
                    temp->a[i][k] *= chromaCzfactor;
                    temp->b[i][k] *= chromaCzfactor;
                }


                if (lochhCurvejz && HHcurvejz) { // Hz=f(Hz)
                    float Hz = xatan2f(temp->b[i][k], temp->a[i][k]);
                    const float valparam = 1.4f * (lochhCurvejz[500.f * static_cast<float>(Color::huejz_to_huehsv2((float)Hz))] - 0.5f) + static_cast<float>(Hz);
                    Hz = valparam;

                    if (Hz < 0.0f) {
                        Hz += (2.f * rtengine::RT_PI_F);
                    }

                    float2 sincosval = xsincosf(Hz);
                    temp->a[i][k] = C_z * sincosval.y;
                    temp->b[i][k] = C_z * sincosval.x;
                }
            }
        }

        if (loclhCurvejz && LHcurvejz && softjz > 0.f) {//Guidedilter for artifacts curve J(H)
            float thr = 0.00001f;
            int flag = 2;
            float softjzr = 0.05f * softjz;
            softproc(tempres.get(), temp.get(), softjzr, height, width, 0.000001, 0.00000001, thr, sk, multiThread, flag);
        }


        if ((lochhCurvejz && HHcurvejz) || (locchCurvejz && CHcurvejz)) { //for artifacts curve H(H)
            if (softjz > 0.f) {
                array2D<float> chro(width, height);
                array2D<float> hue(width, height);
                array2D<float> guid(width, height);

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < height; y++) {
                    for (int x = 0; x < width; x++) {
                        hue[y][x] = xatan2f(temp->b[y][x], temp->a[y][x]);
                        chro[y][x] = sqrt(SQR(temp->b[y][x]) + SQR(temp->a[y][x])) / 32768.f;

                        if (hue[y][x] < 0.0f) {
                            hue[y][x] += (2.f * rtengine::RT_PI_F);
                        }

                        hue[y][x] /= (2.f * rtengine::RT_PI_F);
                        guid[y][x] = tempres->L[y][x] / 32768.f;
                    }
                }

                float softr = softjz;
                const float tmpblur = softr < 0.f ? -1.f / softr : 1.f + softr;
                const int r2 = rtengine::max<int>(10 / sk * tmpblur + 0.2f, 1);
                const int r1 = rtengine::max<int>(4 / sk * tmpblur + 0.5f, 1);
                constexpr float epsilmax = 0.0005f;
                constexpr float epsilmin = 0.0000001f;
                constexpr float aepsil = (epsilmax - epsilmin) / 100.f;
                constexpr float bepsil = epsilmin;
                const float epsil = softr < 0.f ? 0.001f : aepsil * softr + bepsil;

                if (lochhCurvejz && HHcurvejz) {
                    rtengine::guidedFilter(guid, hue, hue, r2, 0.5f * epsil, multiThread);
                }

                if (locchCurvejz && CHcurvejz) {
                    rtengine::guidedFilter(guid, chro, chro, r1, 0.4f * epsil, multiThread);
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < height; y++) {
                    for (int x = 0; x < width; x++) {
                        hue[y][x] *= (2.f * rtengine::RT_PI_F);
                        chro[y][x] *= 32768.f;
                        float2 sincosval = xsincosf(hue[y][x]);
                        temp->a[y][x] = chro[y][x] * sincosval.y;
                        temp->b[y][x] = chro[y][x] * sincosval.x;
                    }
                }
            }
        }


///////////////////


#ifdef _OPENMP
        #pragma omp parallel  for if(multiThread)
#endif

        for (int i = 0; i < height; i++) {
            for (int k = 0; k < width; k++) {
                //reconvert to double
                if (highhs > 0 || shadhs > 0  || wavcurvejz || mjjz != 0.f || lp.mCjz != 0.f || LHcurvejz || HHcurvejz || CHcurvejz) {
                    //now we work in double necessary for matrix conversion and when in range 0..1 with use of PQ
                    JJz[i][k] = (double)(temp->L[i][k] / (32768.f * (float) to_one));
                    Aaz[i][k] = (double)(temp->a[i][k] / (32768.f * (float) to_one));
                    Bbz[i][k] = (double)(temp->b[i][k] / (32768.f * (float) to_one));
                }

                double az =  Aaz[i][k];
                double bz =  Bbz[i][k];
                double Jz =  LIM01(JJz[i][k]);
                Jz *= to_one;
                double Cz = sqrt(az * az + bz * bz);

                //log encoding
                if (logjz) {
                    double jmz =  Jz;

                    if (jmz > noise) {
                        double mm = applytojz(jmz);
                        double f = mm / jmz;
                        Jz *= f;
                        Jz = LIM01(Jz);//clip values
                    }
                }

                //sigmoid 5.12
                if (issigjz12 && iscie && modeqj == 1) { //sigmoid Jz
                    float val = Jz;
                    float Jout = 0.f;
                    sigmoid_QJ(val, Jout, middle_grey_contrastjz, contrast_skewnessjz, middle_greyjz, black_pointjz, white_point_dispjz);

                    Jz = Jout;
                    Jz = LIM01(Jz);
                }

                //sigmoid 5.11
                if (issigjz && iscie && modeqj == 0) { //sigmoid Jz
                    float val = Jz;

                    if (islogjz) {
                        val = std::max((xlog(Jz) / log2 - shadows_range) / (dynamic_range + 1.5), noise);//in range EV
                    }

                    if (sigmoidthjz >= 1.f) {
                        thjz = athjz * val + bthjz;//threshold
                    } else {
                        thjz = atjz * val + btjz;
                    }

                    sigmoidla(val, thjz, sigmjz); //sigmz "slope" of sigmoid


                    Jz = LIM01((double) bljz * Jz + (double) val);
                }

                if (Qtoj == true) { //lightness instead of brightness
                    Jz /= to_one;
                    Jz /= maxjzw;//Jz white
                    Jz = SQR(Jz);
                }

                //contrast
                Jz = LIM01(jz_contrast.getVal(LIM01(Jz)));

                //brightness and lightness
                if (lightreal > 0) {
                    Jz = LIM01(jz_light.getVal(Jz));
                }

                if (lightreal < 0) {
                    Jz = LIM01(jz_lightn.getVal(Jz));
                }

                //Jz (Jz) curve
                double Jzold = Jz;

                if (jzlocalcurve && localjzutili) {
                    Jz = (double)(jzlocalcurve[(float) Jz * 65535.f] / 65535.f);
                    Jz  = 0.3 * (Jz - Jzold) + Jzold;
                }

                //reconvert from lightness or Brightness
                if (Qtoj == false) {
                    Jz /= to_one;
                } else {
                    Jz = sqrt(Jz);
                    Jz *= maxjzw;
                }

                double Hz;
                //remapping Cz
                Hz = xatan2(bz, az);
                double Czold = Cz;

                //Cz(Cz) curve
                if (czlocalcurve && localczutili) {
                    Cz = (double)(czlocalcurve[(float) Cz * 92666.f * (float) to_one] / (92666.f * (float) to_one));
                    Cz  = 0.5 * (Cz - Czold) + Czold;
                }

                //Cz(Jz) curve
                if (czjzlocalcurve && localczjzutili) {
                    double chromaCfactor = (double)(czjzlocalcurve[(float) Jz * 65535.f * (float) to_one]) / (Jz * 65535. * to_one);
                    Cz  *=  chromaCfactor;
                }

                //Hz in 0 2*PI
                if (Hz < 0.0) {
                    Hz += (2. * rtengine::RT_PI);
                }

                //Chroma slider
                if (chromz < 0.) {
                    Cz = Cz * (1. + 0.01 * chromz);
                } else {
                    double maxcz = czlim / to_one;
                    double fcz = Cz / maxcz;
                    double pocz = pow(fcz, 1. - 0.0024 * chromz); //increase value - before 0.0017
                    Cz = maxcz * pocz;
                    //  Cz = Cz * (1. + 0.005 * chromz);//linear
                }

                //saturation slider
                if (saturz != 0.) {
                    double js = Jz / maxjzw; //divide by Jz white
                    js = SQR(js);

                    if (js <= 0.) {
                        js = 0.0000001;
                    }

                    double Sz = Cz / (js);

                    if (saturz < 0.) {
                        Sz = Sz * (1. + 0.01 * saturz);
                    } else {
                        Sz = Sz * (1. + 0.003 * saturz);//not pow function because Sz is "open" - 0.003 empirical value to have results comparable to Cz
                    }

                    Cz = Sz * js;
                }

                //rotation hue
                Hz += dhue;

                if (Hz < 0.0) {
                    Hz += (2. * rtengine::RT_PI);
                }

                Cz = clipcz(Cz);
                double2 sincosval = xsincos(Hz);
                az = clipazbz(Cz * sincosval.y);
                bz = clipazbz(Cz * sincosval.x);
                Cz = sqrt(az * az + bz * bz);


                bz = bz / (to_screen);
                az = az / (to_screen);

                Jz = LIM01(Jz / (to_screen));

                if (jabcie) { //Not used does not work at all
                    Jz = clipjz05(Jz);
                    gamutjz(Jz, az, bz, pl, wip, 0.94, 0.004);
                }

                double L_, M_, S_;
                double xx, yy, zz;
                bool zcam = z_cam;
                //reconvert to XYZ in double
                Ciecam02::jzczhzxyz(xx, yy, zz, Jz, az, bz, pl, L_, M_, S_, zcam);
                //re enable D50
                double x, y, z;
                x = 65535. * (d65_d50[0][0] * xx + d65_d50[0][1] * yy + d65_d50[0][2] * zz);
                y = 65535. * (d65_d50[1][0] * xx + d65_d50[1][1] * yy + d65_d50[1][2] * zz);
                z = 65535. * (d65_d50[2][0] * xx + d65_d50[2][1] * yy + d65_d50[2][2] * zz);

                float Ll, aa, bb;
                Color::XYZ2Lab(x,  y,  z, Ll, aa, bb);
                lab->L[i][k] = Ll;
                lab->a[i][k] = aa;
                lab->b[i][k] = bb;
            }
        }
    }
                    //lp.sursouci==4 disable ciecam
    if ((mocam == 1 && lp.sursouci!= 4)|| mocam ==2 || call == 1  || call == 2 || call == 10) { //CAM16 call=2 vibrance warm-cool - call = 10 take into account "mean luminance Yb for Jz
//begin ciecam
        if (settings->verbose && (mocam == 1  || call == 1)) {//display only if choice cam16
            //information on Cam16 scene conditions - allows user to see choices's incidences
            float maxicamq = -1000.f;
            float maxicamj = -1000.f;
            float maxisat = -1000.f;
            float maxiM = -1000.f;
            float minicam = 1000000.f;
            float minicamq = 1000000.f;
            float minisat = 1000000.f;
            float miniM = 1000000.f;
            int nccam = 0;
            float sumcam = 0.f;
            float sumcamq = 0.f;
            float sumsat = 0.f;
            float sumM = 0.f;

            if (lp.logena && !(params->locallab.spots.at(sp).expcie && mocam == 1)) { //Log encoding only, but enable for log encoding if we use Cam16 module both with log encoding
                plum = 100.f;
            }

            //find main values Cam16
#ifdef _OPENMP
            #pragma omp parallel for reduction(min:minicam) reduction(max:maxicamj) reduction(min:minicamq) reduction(max:maxicamq) reduction(min:minisat) reduction(max:maxisat) reduction(min:miniM) reduction(max:maxiM) reduction(+:sumcam) reduction(+:sumcamq) reduction(+:sumsat) reduction(+:sumM)if(multiThread)
#endif

            for (int i = 0; i < height; i += 1) {
                for (int k = 0; k < width; k += 1) {
                    float L = lab->L[i][k];
                    float a = lab->a[i][k];
                    float b = lab->b[i][k];
                    float x, y, z;
                    //convert Lab => XYZ
                    Color::Lab2XYZ(L, a, b, x, y, z);
                    x = x / 655.35f;
                    y = y / 655.35f;
                    z = z / 655.35f;
                    float J, C, h, Q, M, s;
                    Ciecam02::xyz2jchqms_ciecam02float(J, C,  h,
                                                       Q,  M,  s, aw, fl, wh,
                                                       x,  y,  z,
                                                       xw1, yw1,  zw1,
                                                       c,  nc, pow1, nbb, ncb, pfl, cz, d, c16, plum);

                    if (J > maxicamj) {
                        maxicamj = J;
                    }

                    if (J < minicam) {
                        minicam = J;
                    }

                    sumcam += J;

                    if (Q > maxicamq) {
                        maxicamq = Q;
                    }

                    if (Q < minicamq) {
                        minicamq = Q;
                    }

                    sumcamq += Q;

                    if (s > maxisat) {
                        maxisat = s;
                    }

                    if (s < minisat) {
                        minisat = s;
                    }

                    sumsat += s;

                    if (M > maxiM) {
                        maxiM = M;
                    }

                    if (M < miniM) {
                        miniM = M;
                    }

                    sumM += M;
                }
            }

            nccam = height * width;
            sumcam = sumcam / nccam;
            sumcamq /= nccam;
            sumsat /= nccam;
            sumM /= nccam;
            
            if (settings->verbose) {
                printf("Cam16 Scene  Lighness_J Brightness_Q- HDR-PQ=%5.1f minJ=%3.1f maxJ=%3.1f meanJ=%3.1f minQ=%3.1f maxQ=%4.1f  meanQ=%4.1f meanQ1=%2.3f\n", (double) plum, (double) minicam, (double) maxicamj, (double) sumcam, (double) minicamq, (double) maxicamq, (double) sumcamq, (double) (sumcamq * coefq));
                printf("Cam16 Scene  Saturati-s Colorfulln_M- minSat=%3.1f maxSat=%3.1f meanSat=%3.1f minM=%3.1f maxM=%3.1f meanM=%3.1f\n", (double) minisat, (double) maxisat, (double) sumsat, (double) miniM, (double) maxiM, (double) sumM);
            }
           // maxicam = maxicamq;//maximum Brightness 
            if(sumcamq < maxicamq) {
                // maxicam = sumcamq + 0.2f * minicamq;//maximum Brightness take into account
                 maxicam = sumcamq;//maximum Brightness take into account
                //ponderate maxicam with mean and mini
            } else {
                maxicam = 0.4f * sumcamq + 0.6f * maxicamq;               
            } 
            sumcamq01 = sumcamq * coefq;

        }

        float base = 10.f;
        float linbaseor = 10.f;
        float linbase = 10.f;
        float gray = 15.f;

        const bool compr = params->locallab.spots.at(sp).comprcie > 0.;
        float comprfactor = params->locallab.spots.at(sp).comprcie;
        float comprth = 1.f; //0.1 +  params->locallab.spots.at(sp).comprcieth;

        double drref = 8.5; //Dynamic Range standard
 
        double drd = ((double) dynamic_range - drref) / drref;
 
        double dratt = (double) dynamic_range / drref;
        comprfactor = 0.4f * comprfactor * (float) dratt;//adapt comprfactor to Dynamic Range
        float newgray = 0.18f;


     //   bool logqprov = false; 
        if ((params->locallab.spots.at(sp).logcie && params->locallab.spots.at(sp).logcieq) || mobwev != 0) {//increase Dyn Range when log encoding
            dynamic_range += 0.2;//empirical value
            gray = 0.01f * (float) params->locallab.spots.at(sp).sourceGraycie;
            const float targetgraycie = params->locallab.spots.at(sp).targetGraycie;
            float targetgraycor = 0.01f * targetgraycie;
            base = targetgraycie > 1.f && targetgraycie < 100.f && (float) dynamic_range > 0.f ?  find_gray(std::abs((float) shadows_range) / (float) dynamic_range, (targetgraycor)) : 0.f;
            linbaseor = std::max(base, 2.f);//2. minimal base log to avoid very bad results

            float maxQgray = coefq * maxicam / gray;
            maxicam =  maxQgray;//setting threshold comprcieth
            const float log2 = xlogf(2.f);

            float corlog = xlogf(maxicam)/log2;//correction base logarithme
            linbase = linbaseor / corlog;
            newgray = gray; //gray - 0.022f * (6.f - maxicam);//empirical formula to take into account Q in DR. 6.f  =>approach to mean overall images

            if (settings->verbose) {
                printf("Gray=%1.3f newgray=%1.3f MaxicamQ=%3.2f Base log encode corrected Q=%5.1f Base log encode origig Q=%5.1f\n", (double) gray, (double) newgray, (double) maxicam, (double) linbase, (double) linbaseor);
            }

        }

        const auto applytoq =
        [ = ](float x) -> float {

            x = rtengine::max(x, (float) noise);
            x = rtengine::max(x / newgray, (float) noise);//gray = gain - before log conversion

            if (compr && x >= comprth)//comprth = maxicam
            {
                x = intp(comprfactor, (std::tanh((x - comprth) / comprth) + 1.f) * comprth, x); //as sigmoid... but tanh (tg hyperbolic), inspired by the work of alberto Grigio
            }
        
            x = rtengine::max((xlogf(x) / log2f - (float) shadows_range) / (float) dynamic_range, (float) noise);//x in range EV
            assert(x == x);

            if (linbase > 0.f)//apply log base in function of targetgray blackEvjz and Dynamic Range
            {
                x = xlog2lin(x, linbase);
            }

            return x;
        };

        //prepare Normalize luminance
        float *datain = nullptr;
        float *data = nullptr;
        float *datanorm = nullptr;

        if (((sigmoidnorm  && issigq)  || params->locallab.spots.at(sp).logcieq) && modeqj == 0) {//5.11
            datain = new float[width* height];
            data = new float[width * height];
            datanorm = new float[width * height];
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic, 16)
#endif

            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    datain[(y) * width + (x)] = lab->L[y][x];
                }
            }
        }

#if defined(__SSE2__) || defined(RT_SIMDE)
        int bufferLength = ((width + 3) / 4) * 4; // bufferLength has to be a multiple of 4
#endif
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
#if defined(__SSE2__) || defined(RT_SIMDE)
            // one line buffer per channel and thread
            float Jbuffer[bufferLength] ALIGNED16;
            float Cbuffer[bufferLength] ALIGNED16;
            float hbuffer[bufferLength] ALIGNED16;
            float Qbuffer[bufferLength] ALIGNED16;
            float Mbuffer[bufferLength] ALIGNED16;
            float sbuffer[bufferLength] ALIGNED16;
#endif
#ifdef _OPENMP
            #pragma omp for schedule(dynamic, 16)
#endif

            for (int i = 0; i < height; i++) {
#if defined(__SSE2__) || defined(RT_SIMDE)
                // vectorized conversion from Lab to jchqms
                int k;
                vfloat c655d35 = F2V(655.35f);

                for (k = 0; k < width - 3; k += 4) {
                    vfloat x, y, z;
                    Color::Lab2XYZ(LVFU(lab->L[i][k]), LVFU(lab->a[i][k]), LVFU(lab->b[i][k]), x, y, z);
                    x = x / c655d35;
                    y = y / c655d35;
                    z = z / c655d35;
                    vfloat J, C, h, Q, M, s;
                    Ciecam02::xyz2jchqms_ciecam02float(J, C,  h,
                                                       Q,  M,  s, F2V(aw), F2V(fl), F2V(wh),
                                                       x,  y,  z,
                                                       F2V(xw1), F2V(yw1),  F2V(zw1),
                                                       F2V(c),  F2V(nc), F2V(pow1), F2V(nbb), F2V(ncb), F2V(pfl), F2V(cz), F2V(d), c16, F2V(plum));
                    STVF(Jbuffer[k], J);
                    STVF(Cbuffer[k], C);
                    STVF(hbuffer[k], h);
                    STVF(Qbuffer[k], Q);
                    STVF(Mbuffer[k], M);
                    STVF(sbuffer[k], s);
                }

                for (; k < width; k++) {
                    float L = lab->L[i][k];
                    float a = lab->a[i][k];
                    float b = lab->b[i][k];
                    float x, y, z;
                    //convert Lab => XYZ
                    Color::Lab2XYZ(L, a, b, x, y, z);
                    x = x / 655.35f;
                    y = y / 655.35f;
                    z = z / 655.35f;
                    float J, C, h, Q, M, s;
                    Ciecam02::xyz2jchqms_ciecam02float(J, C,  h,
                                                       Q,  M,  s, aw, fl, wh,
                                                       x,  y,  z,
                                                       xw1, yw1,  zw1,
                                                       c,  nc, pow1, nbb, ncb, pfl, cz, d, c16, plum);
                    Jbuffer[k] = J;
                    Cbuffer[k] = C;
                    hbuffer[k] = h;
                    Qbuffer[k] = Q;
                    Mbuffer[k] = M;
                    sbuffer[k] = s;
                }

#endif // __SSE2__

                for (int j = 0; j < width; j++) {
                    float J, C, h, Q, M, s;

#if defined(__SSE2__) || defined(RT_SIMDE)
                    // use precomputed values from above
                    J = Jbuffer[j];
                    C = Cbuffer[j];
                    h = hbuffer[j];
                    Q = Qbuffer[j];
                    M = Mbuffer[j];
                    s = sbuffer[j];
#else
                    float x, y, z;
                    float L = lab->L[i][j];
                    float a = lab->a[i][j];
                    float b = lab->b[i][j];
                    float x1, y1, z1;
                    //convert Lab => XYZ
                    Color::Lab2XYZ(L, a, b, x1, y1, z1);
                    x = x1 / 655.35f;
                    y = y1 / 655.35f;
                    z = z1 / 655.35f;
                    //process source==> normal
                    Ciecam02::xyz2jchqms_ciecam02float(J, C,  h,
                                                       Q,  M,  s, aw, fl, wh,
                                                       x,  y,  z,
                                                       xw1, yw1,  zw1,
                                                       c,  nc, pow1, nbb, ncb, pfl, cz, d, c16, plum);
#endif
                    float Jpro, Cpro, hpro, Qpro, Mpro, spro;
                    Jpro = J;
                    Cpro = C;
                    hpro = h;
                    Qpro = Q;
                    Mpro = M;
                    spro = s;

                    if (ciec  && mocam == 1) {//only Cam16
                        bool jp = false;

                      //  if (params->locallab.spots.at(sp).logcie && iscie) {//log encoding Q
                        if (params->locallab.spots.at(sp).logcie && params->locallab.spots.at(sp).logcieq && iscie  && modeqj == 0) {//log encoding Q 5.11
             
                            float val =  Qpro *  coefq;

                            if (val > (float) noise) {
                                float mm = applytoq(val);
                                float f = mm / val;
                                Qpro *=  f;
                            }
                        }
                        if (issig && issigq12 && iscie && modeqj == 1) { //sigmoid Q and slope based Q 5.12
                            float val = Qpro * coefq;
                            float Qout = 0.f;
                            if(mobwev12 == 0) {
                                sigmoid_QJ(val, Qout, middle_grey_contrast, contrast_skewness, middle_grey, black_point, white_point_disp);
                            }
                            if(mobwev12 == 1) {
                                bool rolloff = false;//all range
                                bool kmid = false;//not take into account Yb viewing
                                tonemapFreemanQ(val, Qout, slopsmootq , white_pointsig, black_point, middle_grey, mid_gray_view, rolloff, kmid);
                            }

                            Qpro = std::max(Qout / coefq, 0.f);
                            Jpro = SQR((10.f * Qpro) / wh);

                        }

                        if (issig && issigq && iscie && mobwev != 2 && modeqj == 0) { //sigmoid Q only and black Ev & white Ev 5.11

                           float val = Qpro * coefq;

                            if (mobwev == 1) {
                                val = std::max((xlog(val) / log2 - shadows_range) / (dynamic_range + 1.5), noise);//in range EV
                            }

                            float sigreal = sigmoidth * sumcamq01;//correction for sigmoid Q take into account mean Q

                            if (sigreal >= 1.f) {
                                th = (sigreal - 1.f) * val + 1.f;
                            } else {
                                th = (1.f - sigreal) * val + sigreal;
                            }

                            sigmoidla(val, th, sigm);
                            Qpro = std::max(Qpro + val / coefq, 0.f);
                            Qpro = CAMBrightCurveQsig[(float)(Qpro * coefQ)] / coefQ;   //brightness and contrast
                            
                            Jpro = SQR((10.f * Qpro) / wh);

                        }

                        if ((cielocalcurve && localcieutili) && mecamcurve == 1) {//curve Q
                            jp = true;
                            float Qq = Qpro * coefQ;
                            float Qold = Qpro;
                            Qq = 0.5f * cielocalcurve[Qq * 2.f];
                            Qq = Qq / coefQ;
                            Qpro = 0.2f * (Qq - Qold) + Qold;

                            if (jp) {
                                Jpro = SQR((10.f * Qpro) / wh);
                            }
                        }

                        Qpro = CAMBrightCurveQ[(float)(Qpro * coefQ)] / coefQ;   //brightness and contrast

                        float Mp, sres;
                        Mp = Mpro / 100.0f;
                        Ciecam02::curvecolorfloat(mchr, Mp, sres, 2.5f);
                        float dred = 100.f; //in C mode
                        float protect_red = 80.0f; // in C mode
                        dred *= coe; //in M mode
                        protect_red *= coe; //M mode
                        Color::skinredfloat(Jpro, hpro, sres, Mp, dred, protect_red, 0, rstprotection, 100.f, Mpro);
                        Jpro = SQR((10.f * Qpro) / wh);
                        Qpro = (Qpro == 0.f ? epsil : Qpro); // avoid division by zero
                        spro = 100.0f * sqrtf(Mpro / Qpro);
                        Jpro = CAMBrightCurveJ[(float)(Jpro * 327.68f)];   //lightness CIECAM02 + contrast
                        float Sp = spro / 100.0f;
                        Ciecam02::curvecolorfloat(schr, Sp, sres, 1.5f);
                        dred = 100.f; // in C mode
                        protect_red = 80.0f; // in C mode
                        dred = 100.0f * sqrtf((dred * coe) / Q);
                        protect_red = 100.0f * sqrtf((protect_red * coe) / Q);
                        Color::skinredfloat(Jpro, hpro, sres, Sp, dred, protect_red, 0, rstprotection, 100.f, spro);
                        Qpro = QproFactor * sqrtf(Jpro);
                        float Cp = (spro * spro * Qpro) / (1000000.f);
                        Cpro = Cp * 100.f;
                        Ciecam02::curvecolorfloat(cchr, Cp, sres, 1.8f);
                        Color::skinredfloat(Jpro, hpro, sres, Cp, 55.f, 30.f, 1, rstprotection, 100.f, Cpro);

                        hpro = hpro + hue;

                        if (hpro < 0.0f) {
                            hpro += 360.0f;    //hue
                        }

                        if ((cielocalcurve && localcieutili) && mecamcurve == 0) {//curve J
                            float Jj = (float) Jpro * 327.68f;
                            float Jold = Jj;
                            Jj = 0.5f * cielocalcurve[Jj * 2.f];
                            Jj = 0.3f * (Jj - Jold) + Jold;    //divide sensibility
                            Jpro = (float)(Jj / 327.68f);

                            if (Jpro < 1.f) {
                                Jpro = 1.f;
                            }
                        }

                        if (cielocalcurve2 && localcieutili2) {//chroma saturation colorfullness
                            if (mecamcurve2 == 0) {//chroma
                                float parsat = 0.8f; //0.68;
                                float coef = 327.68f / parsat;
                                float Cc = (float) Cpro * coef;
                                float Ccold = Cc;
                                Cc = 0.5f * cielocalcurve2[Cc * 2.f];
                                float dred = 55.f;
                                float protect_red = 30.0f;
                                int sk1 = 1;
                                float ko = 1.f / coef;
                                Color::skinredfloat(Jpro, hpro, Cc, Ccold, dred, protect_red, sk1, rstprotection, ko, Cpro);
                            } else if (mecamcurve2 == 1) {//saturation
                                float parsat = 0.8f; //0.6
                                float coef = 327.68f / parsat;
                                float Ss = (float) spro * coef;
                                float Sold = Ss;
                                Ss = 0.5f * cielocalcurve2[Ss * 2.f];
                                Ss = 0.6f * (Ss - Sold) + Sold; //divide sensibility saturation
                                float dred = 100.f; // in C mode
                                float protect_red = 80.0f; // in C mode
                                dred = 100.0f * sqrtf((dred * coe) / Qpro);
                                protect_red = 100.0f * sqrtf((protect_red * coe) / Qpro);
                                float ko = 1.f / coef;
                                Color::skinredfloat(Jpro, hpro, Ss, Sold, dred, protect_red, 0, rstprotection, ko, spro);
                                Qpro = (4.0f / c) * sqrtf(Jpro / 100.0f) * (aw + 4.0f) ;
                                Cpro = (spro * spro * Qpro) / (10000.0f);
                            } else if (mecamcurve2 == 2) {//colorfullness
                                float parsat = 0.8f; //0.68;
                                float coef = 327.68f / parsat;
                                float Mm = (float) Mpro * coef;
                                float Mold = Mm;
                                Mm = 0.5f * cielocalcurve2[Mm * 2.f];
                                float dred = 100.f; //in C mode
                                float protect_red = 80.0f; // in C mode
                                dred *= coe; //in M mode
                                protect_red *= coe;
                                float ko = 1.f / coef;
                                Color::skinredfloat(Jpro, hpro, Mm, Mold, dred, protect_red, 0, rstprotection, ko, Mpro);
                                Cpro = Mpro / coe;
                            }
                        }
                    }

                    //retrieve values C,J...s
                    C = Cpro;
                    J = Jpro;
                    Q = Qpro;
                    M = Mpro;
                    h = hpro;
                    s = spro;

#if defined(__SSE2__) || defined(RT_SIMDE)
                    // write to line buffers
                    Jbuffer[j] = J;
                    Cbuffer[j] = C;
                    hbuffer[j] = h;
#else
                    float xx, yy, zz;
                    //process normal==> viewing

                    Ciecam02::jch2xyz_ciecam02float(xx, yy, zz,
                                                    J,  C, h,
                                                    xw2, yw2,  zw2,
                                                    c2, nc2,  pow1n, nbbj, ncbj, flj, czj, dj, awj, c16, plum);
                    x = CLIP(xx * 655.35f);
                    y = CLIP(yy * 655.35f);
                    z = CLIP(zz * 655.35f);
                    float Ll, aa, bb;
                    //convert xyz=>lab
                    Color::XYZ2Lab(x,  y,  z, Ll, aa, bb);
                    lab->L[i][j] = Ll;
                    lab->a[i][j] = aa;
                    lab->b[i][j] = bb;
#endif
                }

#if defined(__SSE2__) || defined(RT_SIMDE)
                // process line buffers
                float *xbuffer = Qbuffer;
                float *ybuffer = Mbuffer;
                float *zbuffer = sbuffer;

                for (k = 0; k < bufferLength; k += 4) {
                    vfloat x, y, z;
                    Ciecam02::jch2xyz_ciecam02float(x, y, z,
                                                    LVF(Jbuffer[k]), LVF(Cbuffer[k]), LVF(hbuffer[k]),
                                                    F2V(xw2), F2V(yw2), F2V(zw2),
                                                    F2V(nc2), F2V(pow1n), F2V(nbbj), F2V(ncbj), F2V(flj), F2V(dj), F2V(awj), F2V(reccmcz), c16, F2V(plum));
                    STVF(xbuffer[k], x * c655d35);
                    STVF(ybuffer[k], y * c655d35);
                    STVF(zbuffer[k], z * c655d35);
                }

                // XYZ2Lab uses a lookup table. The function behind that lut is a cube root.
                // SSE can't beat the speed of that lut, so it doesn't make sense to use SSE
                for (int j = 0; j < width; j++) {
                    float Ll, aa, bb;
                    //convert xyz=>lab
                    xbuffer[j] = CLIP(xbuffer[j]);
                    ybuffer[j] = CLIP(ybuffer[j]);
                    zbuffer[j] = CLIP(zbuffer[j]);

                    Color::XYZ2Lab(xbuffer[j], ybuffer[j], zbuffer[j], Ll, aa, bb);

                    lab->L[i][j] = Ll;
                    lab->a[i][j] = aa;
                    lab->b[i][j] = bb;
                }

#endif
            }
        }
         if (((mocam == 1 && (sigmoidnorm && issigq)) || params->locallab.spots.at(sp).logcieq) && modeqj == 0) { //Normalize luminance 5.11

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic, 16)
#endif

            for (int y = 0; y < height; y++) { //data after ciecam
                for (int x = 0; x < width; x++) {
                    data[(y) * width + (x)] = lab->L[y][x];
                    datanorm[(y) * width + (x)] = lab->L[y][x];

                }
            }

            double nbs = 1.;
            drd = std::max(drd, 1.); 
            if (bl > 0.5f) {
                nbs = (1.7 * (double) bl * drd);//take into account DR to increase variance in image source
            }
            if(!params->locallab.spots.at(sp).logcieq) {// not with log encoding Q
                normalize_mean_dt(datanorm, datain, height * width, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, nbs);//normalize luminance
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic, 16)
#endif

            for (int ir = 0; ir < height; ir++) {
                for (int jr = 0; jr < width; jr++) {
                    if(!params->locallab.spots.at(sp).logcieq) {// if not Log encoding ciecam
                        data[ir * width + jr] = intp(bl, data[ir * width + jr], datanorm[ir * width + jr]);//blend with original
                    } else {
                        data[ir * width + jr] = intp(bl, data[ir * width + jr], datain[ir * width + jr]);//blend with original
                    }
                    lab->L[ir][jr] = data[ir * width + jr];
                }
            }
        }

        delete [] datain;
        delete [] data;
        delete [] datanorm;
       
        
        
    }



}

void ImProcFunctions::softproc(const LabImage* bufcolorig, const LabImage* bufcolfin, float rad, int bfh, int bfw, float epsilmax, float epsilmin, float thres, int sk, bool multiThread, int flag)
{
    if (rad != 0.f) {
        array2D<float> ble(bfw, bfh);
        array2D<float> guid(bfw, bfh);

        if (flag == 0) {

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    guid[ir][jr] = Color::L2Y(bufcolorig->L[ir][jr]) / 32768.f;
                    ble[ir][jr] = Color::L2Y(bufcolfin->L[ir][jr]) / 32768.f;
                }
            }

            const float aepsil = (epsilmax - epsilmin) / 100.f;
            const float bepsil = epsilmin; //epsilmax - 100.f * aepsil;
            // const float epsil = aepsil * 0.1f * rad + bepsil;
            const float epsil = aepsil * rad + bepsil;
            const float blur = 10.f / sk * (thres + 0.f * rad);

            rtengine::guidedFilter(guid, ble, ble, blur, epsil, multiThread, 4);

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    bufcolfin->L[ir][jr] =  Color::computeXYZ2LabY(32768.f * ble[ir][jr]);
                }
            }
        } else if (flag == 1) {

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    ble[ir][jr] = bufcolfin->L[ir][jr] / 32768.f;
                    guid[ir][jr] = bufcolorig->L[ir][jr] / 32768.f;
                }

            const float aepsil = (epsilmax - epsilmin) / 1000.f;
            const float bepsil = epsilmin; //epsilmax - 100.f * aepsil;
            const float epsil = rad < 0.f ? 0.0001f : aepsil * rad + bepsil;
            const float blur = rad < 0.f ? -1.f / rad : 1.f + rad;
            const int r2 = rtengine::max(int(25 / sk * blur + 0.5f), 1);

            rtengine::guidedFilter(guid, ble, ble, r2, epsil, multiThread);

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    bufcolfin->L[ir][jr] =  32768.f * ble[ir][jr];
                }
            }
        } else if (flag == 2) {

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    ble[ir][jr] = bufcolfin->L[ir][jr] / 32768.f;
                    guid[ir][jr] = bufcolorig->L[ir][jr] / 32768.f;
                }

            const float aepsil = (epsilmax - epsilmin) / 1000.f;
            const float bepsil = epsilmin; //epsilmax - 100.f * aepsil;
            const float epsil = rad < 0.f ? 0.0001f : aepsil * 10.f * rad + bepsil;
            //  const float epsil =  bepsil;
            const float blur = rad < 0.f ? -1.f / rad : 0.00001f + rad;
            const int r2 = rtengine::max(int(20.f / sk * blur + 0.000001f), 1);

            rtengine::guidedFilter(guid, ble, ble, r2, epsil, multiThread);

#ifdef _OPENMP
            #pragma omp parallel for if(multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    bufcolfin->L[ir][jr] =  32768.f * ble[ir][jr];
                }
            }
        }
    }
}


void ImProcFunctions::softprocess(const LabImage* bufcolorig, array2D<float> &buflight, float rad, int bfh, int bfw, double epsilmax, double epsilmin,  float thres, int sk, bool multiThread)
{
    float minlig = buflight[0][0];

#ifdef _OPENMP
    #pragma omp parallel for reduction(min:minlig) schedule(dynamic,16) if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            minlig = rtengine::min(buflight[ir][jr], minlig);
        }
    }

    array2D<float> guidsoft(bfw, bfh);

#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            buflight[ir][jr] = LIM01((buflight[ir][jr] - minlig) / (100.f - minlig));
            guidsoft[ir][jr] = bufcolorig->L[ir][jr] / 32768.f;
        }
    }

    double aepsil = (epsilmax - epsilmin) / 90.0;
    double bepsil = epsilmax - 100.0 * aepsil;
    double epsil = aepsil * static_cast<double>(rad) + bepsil;
    float blur = 1.f / sk * (thres + 0.8f * rad);
    guidedFilter(guidsoft, buflight, buflight, blur, epsil,  multiThread, 4);


#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            buflight[ir][jr] = (100.f - minlig) * buflight[ir][jr] + minlig;
        }
    }
}

void ImProcFunctions::exlabLocal(local_params& lp, float strlap, int bfh, int bfw, int bfhr, int bfwr, LabImage* bufexporig, LabImage* lab, const LUTf& hltonecurve, const LUTf& shtonecurve, const LUTf& tonecurve, const float hueref, const float lumaref, const float chromaref)
{
    //BENCHFUN
    //exposure local

    constexpr float maxran = 65536.f;

    if (lp.laplacexp == 0.f) {
        lp.linear = 0.f;
    }

    const float linear = lp.linear;
    int bw = bfw;
    int bh = bfh;

    if (linear > 0.f && lp.expcomp == 0.f) {
        lp.expcomp = 0.001f;
    }

    const bool exec = (lp.expmet == 1 && linear > 0.f && lp.laplacexp > 0.1f);

    if (!exec) { //for standard exposure

        const float cexp_scale = std::pow(2.f, lp.expcomp);
        const float ccomp = (rtengine::max(0.f, lp.expcomp) + 1.f) * lp.hlcomp / 100.f;
        const float cshoulder = ((maxran / rtengine::max(1.0f, cexp_scale)) * (lp.hlcompthr / 200.f)) + 0.1f;
        const float chlrange = maxran - cshoulder;
        const float diffde = 100.f - lp.sensex;//the more scope, the less take into account dE for Laplace

        if (!lp.invex) { // Laplacian not in inverse
            bw = bfwr;
            bh = bfhr;

            //Laplacian PDE before exposure to smooth L, algorithm exposure leads to increase L differences
            const std::unique_ptr<float[]> datain(new float[bfwr * bfhr]);
            const std::unique_ptr<float[]> dataout(new float[bfwr * bfhr]);
            const std::unique_ptr<float[]> dE(new float[bfwr * bfhr]);

            deltaEforLaplace(dE.get(), diffde, bfwr, bfhr, bufexporig, hueref, chromaref, lumaref);

            float alap = strlap * 600.f;
            float blap = strlap * 100.f;
            float aa = (alap - blap) / 50.f;
            float bb = blap - 30.f * aa;

            float lap;

            if (diffde > 80.f) {
                lap = alap;
            } else if (diffde < 30.f) {
                lap = blap;
            } else {
                lap = aa * diffde + bb;
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bfhr; y++) {
                for (int x = 0; x < bfwr; x++) {
                    datain[y * bfwr + x] = bufexporig->L[y][x];
                }
            }

            MyMutex::MyLock lock(*fftwMutex);
            ImProcFunctions::retinex_pde(datain.get(), dataout.get(), bfwr, bfhr, lap, 1.f, dE.get(), 0, 1, 1);//350 arbitrary value about 45% strength Laplacian
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bfhr; y++) {
                for (int x = 0; x < bfwr; x++) {
                    bufexporig->L[y][x] = dataout[y * bfwr + x];
                }
            }

        }

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int ir = 0; ir < bh; ir++) {//for standard with Laplacian in normal and without in inverse
            for (int jr = 0; jr < bw; jr++) {
                float L = bufexporig->L[ir][jr];
                //highlight
                const float hlfactor = (2 * L < MAXVALF ? hltonecurve[2 * L] : CurveFactory::hlcurve(cexp_scale, ccomp, chlrange, 2 * L));
                L *= hlfactor;//approximation but pretty good with Laplacian and L < mean, hl aren't call
                //shadow tone curve
                L *= shtonecurve[2 * L];
                //tonecurve
                lab->L[ir][jr] = 0.5f * tonecurve[2 * L];
            }
        }
    } else if (!lp.invex) { //for PDE algorithms
        constexpr float kl = 1.f;
        const float hlcompthr = lp.hlcompthr / 200.f;
        const float hlcomp = lp.hlcomp / 100.f;

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int ir = 0; ir < bfh; ir++) {
            for (int jr = 0; jr < bfw; jr++) {
                float L = bufexporig->L[ir][jr];
                const float Llin = LIM01(L / 32768.f);
                const float addcomp = linear * (-kl * Llin + kl);//maximum about 1 . IL
                const float exp_scale = pow_F(2.f, lp.expcomp + addcomp);
                const float shoulder = (maxran / rtengine::max(1.0f, exp_scale)) * hlcompthr + 0.1f;
                const float comp = (rtengine::max(0.f, (lp.expcomp + addcomp)) + 1.f) * hlcomp;
                const float hlrange = maxran - shoulder;

                //highlight
                const float hlfactor = (2 * L < MAXVALF ? hltonecurve[2 * L] : CurveFactory::hlcurve(exp_scale, comp, hlrange, 2 * L));
                L *= hlfactor * pow_F(2.f, addcomp);//approximation but pretty good with Laplacian and L < mean, hl aren't call
                //shadow tone curve
                L *= shtonecurve[2 * L];
                //tonecurve
                lab->L[ir][jr] = 0.5f * tonecurve[2 * L];
            }
        }
    }
}

void ImProcFunctions::addGaNoise(LabImage *lab, LabImage *dst, const float mean, const float variance, const int sk)
{
//   BENCHFUN
//Box-Muller method.
// add luma noise to image

    srand(1);

    const float variaFactor = SQR(variance) / sk;
    constexpr float randFactor1 = 1.f / RAND_MAX;
    constexpr float randFactor2 = (2.f * rtengine::RT_PI_F) / RAND_MAX;
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        float z0, z1;
        bool generate = false;
#ifdef _OPENMP
        #pragma omp for schedule(static) // static scheduling is important to avoid artefacts
#endif

        for (int y = 0; y < lab->H; y++) {
            for (int x = 0; x < lab->W; x++) {
                generate = !generate;
                float kvar = 1.f;

                if (lab->L[y][x] < 12000.f) {
                    constexpr float ah = -0.5f / 12000.f;
                    constexpr float bh = 1.5f;
                    kvar = ah * lab->L[y][x] + bh;    //increase effect for low lights < 12000.f
                } else if (lab->L[y][x] > 20000.f) {
                    constexpr float ah = -0.5f / 12768.f;
                    constexpr float bh = 1.f - 20000.f * ah;
                    kvar = ah * lab->L[y][x] + bh;    //decrease effect for high lights > 20000.f
                    kvar = kvar < 0.5f ? 0.5f : kvar;
                }

                float varia = SQR(kvar) * variaFactor;

                if (!generate) {
                    dst->L[y][x] = LIM(lab->L[y][x] + mean + varia * z1, 0.f, 32768.f);
                    continue;
                }

                int u1 = 0;
                int u2;

                while (u1 == 0) {
                    u1 = rand();
                    u2 = rand();
                }

                float u1f = u1 * randFactor1;
                float u2f = u2 * randFactor2;

                float2 sincosval = xsincosf(2.f * rtengine::RT_PI_F * u2f);
                float factor = std::sqrt(-2.f * xlogf(u1f));
                z0 = factor * sincosval.y;
                z1 = factor * sincosval.x;

                dst->L[y][x] = LIM(lab->L[y][x] + mean + varia * z0, 0.f, 32768.f);

            }
        }
    }
}


void ImProcFunctions::DeNoise_Local(int call, const struct local_params& lp, LabImage* originalmask, int levred, float hueref, float lumaref, float chromaref, LabImage* original, LabImage* transformed, const LabImage &tmp1, int cx, int cy, int sk)
{
    //warning, but I hope used it next
    // local denoise and impulse
    //simple algo , perhaps we can improve as the others, but noise is here and not good for hue detection
    // BENCHFUN
    lumaref *= 327.68f;
    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disabled transit
        ach = 1.f;
    }

    const float factnoise1 = 1.f + (lp.noisecf) / 500.f;
    const float factnoise2 = 1.f + (lp.noisecc) / 500.f;
    const float factnoise = factnoise1 * factnoise2;

    const int GW = transformed->W;
    const int GH = transformed->H;

    const float colorde = lp.colorde == 0 ? -1.f : lp.colorde; // -1.f to avoid black
    const float amplabL = 2.f * colorde;
    constexpr float darklim = 5000.f;

    const float refa = chromaref * std::cos(hueref) * 327.68f;
    const float refb = chromaref * std::sin(hueref) * 327.68f;
    const bool usemaskbl = lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 4;
    const bool blshow = lp.showmaskblmet == 1 || lp.showmaskblmet == 2;
    const bool previewbl = lp.showmaskblmet == 4;

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
    const float radius = 3.f / sk;

    if (usemaskbl) {
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblur->L, GW, GH, radius);
            gaussianBlur(originalmask->a, origblur->a, GW, GH, radius);
            gaussianBlur(originalmask->b, origblur->b, GW, GH, radius);
        }
    } else {
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(original->L, origblur->L, GW, GH, radius);
            gaussianBlur(original->a, origblur->a, GW, GH, radius);
            gaussianBlur(original->b, origblur->b, GW, GH, radius);
        }
    }

    const int begx = lp.xc - lp.lxL;
    const int begy = lp.yc - lp.lyT;
    constexpr float r327d68 = 1.f / 327.68f;

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const LabImage* maskptr = origblur.get();
        const float mindE = 2.f + MINSCOPE * lp.sensden * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * lp.sensden * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            const int loy = cy + y;
            const bool isZone0 = loy > lp.yc + lp.ly || loy < lp.yc - lp.lyT; // whole line is zone 0 => we can skip a lot of processing

            if (isZone0) { // outside selection and outside transition zone => no effect, keep original values
                continue;
            }

            for (int x = 0, lox = cx + x; x < transformed->W; x++, lox++) {
                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }
                if(lp.fullim == 3 ) {//disabled scope
                    localFactor = 1.f;
                }

                if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

                float reducdEL = 1.f;
                float reducdEa = 1.f;
                float reducdEb = 1.f;

                if (levred == 7) {
                    const float dEL = std::sqrt(0.9f * SQR(refa - maskptr->a[y][x]) + 0.9f * SQR(refb - maskptr->b[y][x]) + 1.2f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    const float dEa = std::sqrt(1.2f * SQR(refa - maskptr->a[y][x]) + 1.f * SQR(refb - maskptr->b[y][x]) + 0.8f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    const float dEb = std::sqrt(1.f * SQR(refa - maskptr->a[y][x]) + 1.2f * SQR(refb - maskptr->b[y][x]) + 0.8f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    reducdEL = SQR(calcreducdE(dEL, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                    reducdEa = SQR(calcreducdE(dEa, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                    reducdEb = SQR(calcreducdE(dEb, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                }

                float difL, difa, difb;

                if (call == 2  /*|| call == 1  || call == 3 */) { //simpleprocess
                    difL = tmp1.L[loy - begy][lox - begx] - original->L[y][x];
                    difa = tmp1.a[loy - begy][lox - begx] - original->a[y][x];
                    difb = tmp1.b[loy - begy][lox - begx] - original->b[y][x];
                } else  { //dcrop
                    const float repart = 1.0f - 0.01f * lp.reparden;
                    tmp1.L[y][x] = intp(repart, original->L[y][x], tmp1.L[y][x]);
                    tmp1.a[y][x] = intp(repart, original->a[y][x], tmp1.a[y][x]);
                    tmp1.b[y][x] = intp(repart, original->b[y][x], tmp1.b[y][x]);

                    difL = tmp1.L[y][x] - original->L[y][x];
                    difa = tmp1.a[y][x] - original->a[y][x];
                    difb = tmp1.b[y][x] - original->b[y][x];
                }
                if(lp.fullim == 3 ) {//disable scope
                    reducdEL = reducdEa = reducdEb = 1.f;
                }

                difL *= localFactor * reducdEL;
                difa *= localFactor * reducdEa;
                difb *= localFactor * reducdEb;
                transformed->L[y][x] = CLIP(original->L[y][x] + difL);
                transformed->a[y][x] = clipC((original->a[y][x] + difa) * factnoise);
                transformed->b[y][x] = clipC((original->b[y][x] + difb) * factnoise) ;
                if (blshow  && lp.fullim != 3) {
                    transformed->L[y][x] = CLIP(12000.f + amplabL * difL);// * 10.f empirical to can visualize modifications
                    transformed->a[y][x] = clipC(amplabL * difa);// * 10.f empirical to can visualize modifications
                    transformed->b[y][x] = clipC(amplabL * difb);// * 10.f empirical to can visualize modifications
                } else if (previewbl || lp.prevdE) {
                    const float difbdisp = (reducdEL + reducdEa + reducdEb) * 10000.f * colorde;

                    if (transformed->L[y][x] < darklim) { //enhance dark luminance as user can see!
                        transformed->L[y][x] = darklim - transformed->L[y][x];
                    }

                    if (colorde <= 0) {
                        transformed->a[y][x] = 0.f;
                        transformed->b[y][x] = difbdisp;
                    } else {
                        transformed->a[y][x] = -difbdisp;
                        transformed->b[y][x] = 0.f;
                    }
                }
            }
        }
    }
}

void ImProcFunctions::DeNoise_Local2(const struct local_params& lp, LabImage* originalmask, int levred, float hueref, float lumaref, float chromaref, LabImage* original, LabImage* transformed, const LabImage &tmp1, int cx, int cy, int sk)
{
    //warning, but I hope used it next
    // local denoise and impulse
    //simple algo , perhaps we can improve as the others, but noise is here and not good for hue detection
    // BENCHFUN
    const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
    const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
    const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
    const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);


    lumaref *= 327.68f;
    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disabled transit
        ach = 1.f;
    }

    const float factnoise1 = 1.f + (lp.noisecf) / 500.f;
    const float factnoise2 = 1.f + (lp.noisecc) / 500.f;
    const float factnoise = factnoise1 * factnoise2;

    const int GW = transformed->W;
    const int GH = transformed->H;

    const float colorde = lp.colorde == 0 ? -1.f : lp.colorde; // -1.f to avoid black
    const float amplabL = 2.f * colorde;
    constexpr float darklim = 5000.f;

    const float refa = chromaref * std::cos(hueref) * 327.68f;
    const float refb = chromaref * std::sin(hueref) * 327.68f;
    const bool usemaskbl = lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 4;
    const bool blshow = lp.showmaskblmet == 1 || lp.showmaskblmet == 2;
    const bool previewbl = lp.showmaskblmet == 4;

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
    const float radius = 3.f / sk;

    if (usemaskbl) {
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblur->L, GW, GH, radius);
            gaussianBlur(originalmask->a, origblur->a, GW, GH, radius);
            gaussianBlur(originalmask->b, origblur->b, GW, GH, radius);
        }
    } else {
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(original->L, origblur->L, GW, GH, radius);
            gaussianBlur(original->a, origblur->a, GW, GH, radius);
            gaussianBlur(original->b, origblur->b, GW, GH, radius);
        }
    }

//   const int begx = lp.xc - lp.lxL;
//   const int begy = lp.yc - lp.lyT;
    constexpr float r327d68 = 1.f / 327.68f;

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const LabImage* maskptr = origblur.get();
        const float mindE = 2.f + MINSCOPE * lp.sensden * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * lp.sensden * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = ystart; y < yend; y++) {
            const int loy = cy + y;
//            const bool isZone0 = loy > lp.yc + lp.ly || loy < lp.yc - lp.lyT; // whole line is zone 0 => we can skip a lot of processing

//            if (isZone0) { // outside selection and outside transition zone => no effect, keep original values
//                continue;
//           }

            for (int x = xstart, lox = cx + x; x < xend; x++, lox++) {
                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }
                if(lp.fullim == 3 ) {//disabled scope
                    localFactor = 1.f;
                }

                if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

                float reducdEL = 1.f;
                float reducdEa = 1.f;
                float reducdEb = 1.f;

                if (levred == 7) {
                    const float dEL = std::sqrt(0.9f * SQR(refa - maskptr->a[y][x]) + 0.9f * SQR(refb - maskptr->b[y][x]) + 1.2f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    const float dEa = std::sqrt(1.2f * SQR(refa - maskptr->a[y][x]) + 1.f * SQR(refb - maskptr->b[y][x]) + 0.8f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    const float dEb = std::sqrt(1.f * SQR(refa - maskptr->a[y][x]) + 1.2f * SQR(refb - maskptr->b[y][x]) + 0.8f * SQR(lumaref - maskptr->L[y][x])) * r327d68;
                    reducdEL = SQR(calcreducdE(dEL, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                    reducdEa = SQR(calcreducdE(dEa, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                    reducdEb = SQR(calcreducdE(dEb, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensden));
                }

                float difL, difa, difb;

                const float repart = 1.0f - 0.01f * lp.reparden;
                tmp1.L[y - ystart][x - xstart] = intp(repart, original->L[y][x], tmp1.L[y - ystart][x - xstart]);
                tmp1.a[y - ystart][x - xstart] = intp(repart, original->a[y][x], tmp1.a[y - ystart][x - xstart]);
                tmp1.b[y - ystart][x - xstart] = intp(repart, original->b[y][x], tmp1.b[y - ystart][x - xstart]);

                difL = tmp1.L[y - ystart][x - xstart] - original->L[y][x];
                difa = tmp1.a[y - ystart][x - xstart] - original->a[y][x];
                difb = tmp1.b[y - ystart][x - xstart] - original->b[y][x];
                if(lp.fullim == 3 ) {//disable scope
                    reducdEL = reducdEa = reducdEb = 1.f;
                }

                difL *= localFactor * reducdEL;
                difa *= localFactor * reducdEa;
                difb *= localFactor * reducdEb;
                transformed->L[y][x] = CLIP(original->L[y][x] + difL);
                transformed->a[y][x] = clipC((original->a[y][x] + difa) * factnoise);
                transformed->b[y][x] = clipC((original->b[y][x] + difb) * factnoise) ;

                if (blshow && lp.fullim != 3) {
                    transformed->L[y][x] = CLIP(12000.f + amplabL * difL);// * 10.f empirical to can visualize modifications
                    transformed->a[y][x] = clipC(amplabL * difa);// * 10.f empirical to can visualize modifications
                    transformed->b[y][x] = clipC(amplabL * difb);// * 10.f empirical to can visualize modifications
                } else if (previewbl || lp.prevdE) {
                    const float difbdisp = (reducdEL + reducdEa + reducdEb) * 10000.f * colorde;

                    if (transformed->L[y][x] < darklim) { //enhance dark luminance as user can see!
                        transformed->L[y][x] = darklim - transformed->L[y][x];
                    }

                    if (colorde <= 0) {
                        transformed->a[y][x] = 0.f;
                        transformed->b[y][x] = difbdisp;
                    } else {
                        transformed->a[y][x] = -difbdisp;
                        transformed->b[y][x] = 0.f;
                    }
                }
            }
        }
    }
}


void ImProcFunctions::InverseReti_Local(const struct local_params & lp, const float hueref, const float chromaref,  const float lumaref, LabImage * original, LabImage * transformed, const LabImage * const tmp1, int cx, int cy, int chro, int sk)
{
    // BENCHFUN
//inverse local retinex
    float ach = lp.trans / 100.f;
    int GW = transformed->W;
    int GH = transformed->H;
    float refa = chromaref * cos(hueref);
    float refb = chromaref * sin(hueref);

    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));

    float radius = 3.f / sk;
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);

    }
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const float mindE = 2.f + MINSCOPE * lp.sensh * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * lp.sensh * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            int loy = cy + y;

            for (int x = 0; x < transformed->W; x++) {
                int lox = cx + x;

                int zone;
                float localFactor;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }

                float rL = origblur->L[y][x] / 327.68f;
                float dE = std::sqrt(kab * SQR(refa - origblur->a[y][x] / 327.68f) + kab * SQR(refb - origblur->b[y][x] / 327.68f) + kL * SQR(lumaref - rL));
                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensh);
                if(lp.fullim == 3 ) {//disable scope
                    reducdE = 1.f;
                }

                switch (zone) {
                    case 0: { // outside selection and outside transition zone => full effect, no transition
                        if (chro == 0) {
                            float difL = tmp1->L[y][x] - original->L[y][x];
                            transformed->L[y][x] = CLIP(original->L[y][x] + difL * reducdE);
                        }

                        if (chro == 1) {
                            float difa = tmp1->a[y][x] - original->a[y][x];
                            float difb = tmp1->b[y][x] - original->b[y][x];

                            transformed->a[y][x] = clipC(original->a[y][x] + difa * reducdE);
                            transformed->b[y][x] = clipC(original->b[y][x] + difb * reducdE);
                        }

                        break;
                    }

                    case 1: { // inside transition zone
                        float factorx = 1.f - localFactor;

                        if (chro == 0) {
                            float difL = tmp1->L[y][x] - original->L[y][x];
                            difL *= factorx;
                            transformed->L[y][x] = CLIP(original->L[y][x] + difL * reducdE);
                        }

                        if (chro == 1) {
                            float difa = tmp1->a[y][x] - original->a[y][x];
                            float difb = tmp1->b[y][x] - original->b[y][x];

                            difa *= factorx;
                            difb *= factorx;

                            transformed->a[y][x] = clipC(original->a[y][x] + difa * reducdE);
                            transformed->b[y][x] = clipC(original->b[y][x] + difb * reducdE);
                        }

                        break;
                    }

                    case 2: { // inside selection => no effect, keep original values
                        if (chro == 0) {
                            transformed->L[y][x] = original->L[y][x];
                        }

                        if (chro == 1) {
                            transformed->a[y][x] = original->a[y][x];
                            transformed->b[y][x] = original->b[y][x];
                        }
                    }
                }
            }
        }
    }
}



void ImProcFunctions::InverseBlurNoise_Local(LabImage * originalmask, const struct local_params & lp,  const float hueref, const float chromaref, const float lumaref, LabImage * original, LabImage * transformed, const LabImage * const tmp1, int cx, int cy, int sk)
{
    // BENCHFUN
//inverse local blur and noise
    float ach = lp.trans / 100.f;
    int GW = transformed->W;
    int GH = transformed->H;
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;


    const bool blshow = (lp.showmaskblmet == 1 || lp.showmaskblmet == 2);
    const bool previewbl = (lp.showmaskblmet == 4);

    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
    std::unique_ptr<LabImage> origblurmask;
    const bool usemaskbl = (lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 4);
    const bool usemaskall = usemaskbl;

    float radius = 3.f / sk;

    if (usemaskall) {
        origblurmask.reset(new LabImage(GW, GH));

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblurmask->L, GW, GH, radius);
            gaussianBlur(originalmask->a, origblurmask->a, GW, GH, radius);
            gaussianBlur(originalmask->b, origblurmask->b, GW, GH, radius);
        }
    }


#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);

    }
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const LabImage *maskptr = usemaskall ? origblurmask.get() : origblur.get();
        const float mindE = 2.f + MINSCOPE * lp.sensbn * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * lp.sensbn * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            int loy = cy + y;

            for (int x = 0; x < transformed->W; x++) {
                int lox = cx + x;

                int zone;
                float localFactor;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else {
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }

                float reducdE;

                if (zone != 2) {
                    float abdelta2 = SQR(refa - maskptr->a[y][x]) + SQR(refb - maskptr->b[y][x]);
                    float chrodelta2 = SQR(std::sqrt(SQR(maskptr->a[y][x]) + SQR(maskptr->b[y][x])) - (chromaref * 327.68f));
                    float huedelta2 = abdelta2 - chrodelta2;
                    float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - maskptr->L[y][x]));
                    reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensbn);
                    if(lp.fullim == 3 ) {//disabled scope
                        reducdE = 1.f;
                    }
                   
                }

                switch (zone) {

                    case 0: { // outside selection and outside transition zone => full effect, no transition
                        const float diflc = (tmp1->L[y][x] - original->L[y][x]) * reducdE;
                        const float difa = (tmp1->a[y][x] - original->a[y][x]) * reducdE;
                        const float difb = (tmp1->b[y][x] - original->b[y][x]) * reducdE;
                        transformed->L[y][x] = CLIP(original->L[y][x] + diflc);
                        transformed->a[y][x] = clipC(original->a[y][x] + difa) ;
                        transformed->b[y][x] = clipC(original->b[y][x] + difb);

                        if (blshow && lp.fullim != 3) {
                            transformed->L[y][x] = CLIP(12000.f + diflc);
                            transformed->a[y][x] = clipC(difa);
                            transformed->b[y][x] = clipC(difb);
                        } else if (previewbl || lp.prevdE) {
                            transformed->a[y][x] = 0.f;
                            transformed->b[y] [x] = (difb);
                        }

                        break;
                    }

                    case 1: { // inside transition zone
                        const float factorx = 1.f - localFactor;

                        const float diflc = (tmp1->L[y][x] - original->L[y][x]) * (reducdE * factorx);
                        const float difa = (tmp1->a[y][x] - original->a[y][x]) * (reducdE * factorx);
                        const float difb = (tmp1->b[y][x] - original->b[y][x]) * (reducdE * factorx);
                        transformed->L[y][x] = CLIP(original->L[y][x] + diflc);
                        transformed->a[y][x] = clipC(original->a[y][x] + difa) ;
                        transformed->b[y][x] = clipC(original->b[y][x] + difb);

                        if (blshow  && lp.fullim != 3) {
                            transformed->L[y][x] = CLIP(12000.f + diflc);
                            transformed->a[y][x] = clipC(difa);
                            transformed->b[y][x] = clipC(difb);
                        } else if (previewbl) {
                            transformed->a[y][x] = 0.f;
                            transformed->b[y][x] = (difb);
                        }

                        break;
                    }

                    case 2: { // outside selection and outside transition zone => no effect, keep original values
                        transformed->L[y][x] = original->L[y][x];
                        transformed->a[y][x] = original->a[y][x];
                        transformed->b[y][x] = original->b[y][x];
                    }
                }
            }
        }
    }
}



static void mean_fab(int xstart, int ystart, int bfw, int bfh, LabImage* bufexporig, int flag, const LabImage* original, float &fab, float &meanfab, float &maxfab, float chrom, bool multiThread)
{
    const int nbfab = bfw * bfh;

    meanfab = 0.f;
    fab = 50.f;

    if (nbfab > 0) {
        double sumab = 0.0;
#ifdef _OPENMP
        #pragma omp parallel for reduction(+:sumab) if(multiThread)
#else
        static_cast<void>(multiThread);
#endif

        for (int y = 0; y < bfh; y++) {
            for (int x = 0; x < bfw; x++) {
                if (flag == 0) {
                    bufexporig->a[y][x] = original->a[y + ystart][x + xstart];
                    bufexporig->b[y][x] = original->b[y + ystart][x + xstart];
                } else {
                    bufexporig->a[y][x] = original->a[y][x];
                    bufexporig->b[y][x] = original->b[y][x];
                }

                sumab += static_cast<double>(SQR(std::fabs(bufexporig->a[y][x])) + SQR(std::fabs(bufexporig->b[y][x])));

                //  sumab += static_cast<double>(std::fabs(bufexporig->a[y][x]));
                //  sumab += static_cast<double>(std::fabs(bufexporig->b[y][x]));
            }
        }

        meanfab = sqrt(sumab / (2.0 * nbfab));

        double som = 0.0;
        double maxm = 0.0;
#ifdef _OPENMP
        #pragma omp parallel for reduction(+:som) reduction(max:maxfab)if(multiThread)
#endif

        for (int y = 0; y < bfh; y++) {
            for (int x = 0; x < bfw; x++) {
                som += static_cast<double>(SQR(std::fabs(bufexporig->a[y][x]) - meanfab) + SQR(std::fabs(bufexporig->b[y][x]) - meanfab));
                maxm = static_cast<double>(SQR(std::fabs(bufexporig->a[y][x])) + SQR(std::fabs(bufexporig->b[y][x])));
                maxm = sqrt(maxm);

                if (maxm > (double) maxfab) {
                    maxfab = (float) maxm;
                }

            }
        }

        const float multsigma = 3.f ;//(chrom >= 0.f ? 0.035f : 0.018f) * chrom + 1.f; //disabled an use 2 stddv
        const float kf = 0.f;
        const float multchrom = 1.f + kf * chrom; //small correction chrom here 0.f

        const float stddv = std::sqrt(som / nbfab);
        float fabprov = meanfab + multsigma * stddv * multchrom;//with 3 sigma about 99% cases

        if (fabprov > maxfab) {
            fabprov = maxfab;
        }

        fab = max(fabprov, 0.90f * maxfab); //Find maxi between mean + 3 sigma and 90% max (90 arbitrary empirical value)

        if (fab <= 0.f) {
            fab = 50.f;
        }
    }
}

struct grad_params {
    bool angle_is_zero, transpose, bright_top;
    float ta, yc, xc;
    float ys, ys_inv;
    float scale, botmul, topmul;
    float top_edge_0;
    int h;
};

void calclocalGradientParams(int call, const struct local_params& lp, struct grad_params& gp, float ystart, float xstart, float yend, float xend, int bfw, int bfh, int oW, int oH, int tX, int tY, int tW, int tH, int indic, int sk, int fw, int fh, int cx, int cy, float &ksk, float &kskx, float &kbh, float &kbw)
{
    int w = bfw;//??? oW, tW..
    int h = bfh;//??? oH, tH..
    float stops = 0.f;
    float angs = 0.f;
    double varfeath = 0.25;
 // big problem with preview as soon as the preview no longer covers the entire image
 // I have tried a lot of things adding parameters that may have an impact oW, oH, tX, tY, tW, tH, fW, fH, sk, call, etc.
 // but nothing works really...
 // Perhaps with PreviewProps, but I don't know how to do ? 
 // It seems that you need to change the position of the center of the GF which varies depending on the preview, but how?
 // parameters passe to calcGradientFactor may also be involved

    //others parameters to chnage behavior of GF complete those in beginning of lab_local
    //all these parameters allows to chnage behavior of GF with all factors preview  oW, oH, tX, tY, tW, tH, fW, fH, sk, call, cx, cy, bfw, bfh
    
    int kx = 0;
    int ky = 0;
    //kx ky  - try to take account of position of window in preview
    //acts on center of GF we can easiy chnage - kx in a function of oW, bfw, etc.
    // it is only here in calclocalGradientParams

   // double kstop = 0.5; // to simulate stops in GF main - 0.5 arbitrary only here in calclocalGradientParams
    double kstop = 1.0; // keep all agressive settings - to simulate stops in GF main - 0.5 arbitrary only here in calclocalGradientParams
    
    float ktyoh = 1.f; //acts on cy - Try to take into account position of window in preview with various factors...ex: -2.f * ((float)(oH - tY) / (float)oH);
    float ktxoh = 1.f; //acts on cx - Try to take into account position of window in preview with various factors...ex: -2.f * ((float)(oH - tY) / (float)oH);
    if(sk == 1) {//try empirical to see, not good
        ktyoh = 0.86f;
        ktxoh = 0.86f;
    } else if (sk == 2) {
        ktyoh = 0.95f;
        ktxoh = 0.95f;
    }
    
    ksk *= ktyoh;
    kskx *= ktxoh;
    
    float ktbh = 1.f; //acts on bfw in calcgradientfactor - repartition of gradient
    float ktbw = 1.f; //acts on bfh  in calcgradientfactor - repartition of gradient

    kbh *= ktbh;
    kbw *= ktbw;
   
   // cannot be used for RT-Spot "normal" or "exclude" : only in fullimage or Global
    double gradient_center_x = LIM01((lp.xcent * (oW + kx)) / bfw);//for dcrop and improccordinator
    double gradient_center_y = LIM01((lp.ycent * (oH + ky)) / bfh);
    //This solution works better than the old one, you can move the window, but is still - a little less - with a malfunction with Zoom

/* old settings 5.11 - different from lp.xcent and take into account xstart and ystart
  //  double gradient_center_x = LIM01((lp.xc - xstart) / bfw);
  //  double gradient_center_y = LIM01((lp.yc - ystart) / bfh);
*/

    if(call == 2 || lp.fullim == 0 ) {//simpleprocess or compatibility 5.11 for Spot normal (I don't keep "exclude")
        gradient_center_x = LIM01((lp.xcent * (bfw)) / bfw);//I keep this formula because perhaps not bfw, for the first
        gradient_center_y = LIM01((lp.ycent * (bfh)) / bfh);
    }


    if (indic == 0) {
        stops = -lp.strmaexp;
        angs = lp.angmaexp;
        varfeath = 0.01f * lp.feath;//for all masks when present
    } else if (indic == 1) {
        stops = lp.strexp;
        angs = lp.angexp;
        varfeath = 0.01f * lp.featherexp;
    } else if (indic == 2) {
        stops = lp.strSH;
        angs = lp.angSH;
        varfeath = 0.01f * lp.featherSH;
    } else if (indic == 3) {
        stops = lp.strcol;
        angs = lp.angcol;
        varfeath = 0.01f * lp.feathcol;
    } else if (indic == 4) {
        float redu = 1.f;

        if (lp.strcolab > 0.f) {
            redu = 0.6f;
        } else {
            redu = 0.15f;
        }
        stops = redu * lp.strcolab;
        angs = lp.angcol;
        varfeath = 0.01f * lp.feathcol;
    } else if (indic == 5) {
        stops = lp.strcolab ;
        angs = lp.angcol;
        varfeath = 0.01f * lp.feathcol;
    } else if (indic == 6) {
        stops = lp.strcolh;
        angs = lp.angcol;
        varfeath = 0.01f * lp.feathcol;
    } else if (indic == 7) {
        stops = lp.strvib;
        angs = lp.angvib;
        varfeath = 0.01f * lp.feathervib;
    } else if (indic == 8) {
        float redu = 1.f;
        if (lp.strvibab > 0.f) {
            redu = 0.7f;
        } else {
            redu = 0.5f;
        }
        stops = redu * lp.strvibab;
        angs = lp.angvib;
        varfeath = 0.01f * lp.feathervib;
    } else if (indic == 9) {
        stops = lp.strvibh;
        angs = lp.angvib;
        varfeath = 0.01f * lp.feathervib;
    } else if (indic == 10) {
        stops = std::fabs(lp.strwav);
        angs = lp.angwav;
        varfeath = 0.01f * lp.featherwav;
    } else if (indic == 11) {
        stops = lp.strlog;
        angs = lp.anglog;
        varfeath = 0.01f * lp.featherlog;
    } else if (indic == 12) {
        stops = -lp.str_mas;
        angs = lp.ang_mas;
        varfeath = 0.01f * lp.feather_mas;
    } else if (indic == 15) {
        stops = lp.strgradcie;
        angs = lp.anggradcie;
        varfeath = 0.01f * lp.feathercie;
            }
    int sk2 = 1;//not used very bad behavior
    double gradient_stops = stops / sk2;//I have test with Skip but does not work well, even bad
    
    gradient_stops *= kstop;
  
//hide these informations to clean console, if need 
//all parameters send by Dcrop are here... But it is not evident why it does not work correctly 
    if (settings->verbose) {
        printf("call=%i xcent=%.2f ycent=%.2f Gcx=%.2f Gcy=%.2f indic=%i cx=%i cy=%i strength(stops)=%f k_sk=%f k_skx=%f kbw=%f kbh=%f\n", call, (double) lp.xcent, (double) lp.ycent, gradient_center_x, gradient_center_y, indic, cx, cy, gradient_stops, (double) ksk, (double) kskx, (double) kbw, (double) kbh);   
        printf("fw=%i fh=%i bfw=%i bfh=%i oW=%i oH=%i tW=%i tH=%i tX=%i tY=%i xstart=%.1f ystrat=%.1f xend=%.1f yend=%.1f xc=%.1f yc=%.1f yT=%.1f xL=%.1f sk=%i\n", fw, fh, bfw, bfh, oW, oH, tW, tH, tX, tY, (double) xstart, (double) ystart, (double) xend, (double) yend, (double) lp.xc, (double) lp.yc, (double) lp.lyT, (double)lp.lxL,  sk);
    }

    double gradient_angle = static_cast<double>(angs) / 180.0 * rtengine::RT_PI;

    gradient_angle = fmod(gradient_angle, 2 * rtengine::RT_PI);

    if (gradient_angle < 0.0) {
        gradient_angle += 2.0 * rtengine::RT_PI;
    }

    gp.bright_top = false;
    gp.transpose = false;
    gp.angle_is_zero = false;
    gp.h = h;
    double cosgrad = cos(gradient_angle);

    if (std::fabs(cosgrad) < 0.707) {
        // we transpose to avoid division by zero at 90 degrees
        // (actually we could transpose only for 90 degrees, but this way we avoid
        // division with extremely small numbers
        gp.transpose = true;
        gradient_angle += 0.5 * rtengine::RT_PI;
        double gxc = gradient_center_x;
        gradient_center_x = 1.0 - gradient_center_y;
        gradient_center_y = gxc;
    }

    gradient_angle = fmod(gradient_angle, 2 * rtengine::RT_PI);

    if (gradient_angle > 0.5 * rtengine::RT_PI && gradient_angle < rtengine::RT_PI) {
        gradient_angle += rtengine::RT_PI;
        gp.bright_top = true;
    } else if (gradient_angle >= rtengine::RT_PI && gradient_angle < 1.5 * rtengine::RT_PI) {
        gradient_angle -= rtengine::RT_PI;
        gp.bright_top = true;
    }

    if (std::fabs(gradient_angle) < 0.001 || std::fabs(gradient_angle - 2 * rtengine::RT_PI) < 0.001) {
        gradient_angle = 0;
        gp.angle_is_zero = true;
    }

    if (gp.transpose) {
        gp.bright_top = !gp.bright_top;
        std::swap(w, h);
    }

    gp.scale = 1.0 / pow(2, gradient_stops);

    if (gp.bright_top) {
        gp.topmul = 1.0;
        gp.botmul = gp.scale;
    } else {
        gp.topmul = gp.scale;
        gp.botmul = 1.0;
    }

    gp.ta = tan(gradient_angle);
    gp.xc = w * gradient_center_x;
    gp.yc = h * gradient_center_y;
    gp.ys = std::sqrt(h * h + w * w) * (varfeath / cos(gradient_angle));
    gp.ys_inv = 1.f / gp.ys;
    gp.top_edge_0 = gp.yc - gp.ys / 2.f;

    if (gp.ys < 1.f / h) {
        gp.ys_inv = 0;
        gp.ys = 0;
    }
   
}

void ImProcFunctions::blendstruc(int bfw, int bfh, LabImage* bufcolorig, float radius, float stru, array2D<float> & blend2, int sk, bool multiThread)
{
    SobelCannyLuma(blend2, bufcolorig->L, bfw, bfh, radius);
    float rm = 20.f / sk;

    if (rm > 0) {
        float **mb = blend2;
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(mb, mb, bfw, bfh, rm);
        }
    }

    array2D<float> ble(bfw, bfh);
    array2D<float> guid(bfw, bfh);
#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            float X, Y, Z;
            float L = bufcolorig->L[ir][jr];
            float a = bufcolorig->a[ir][jr];
            float b = bufcolorig->b[ir][jr];
            Color::Lab2XYZ(L, a, b, X, Y, Z);

            guid[ir][jr] = Y / 32768.f;

            blend2[ir][jr] /= 32768.f;
        }
    }

    const float blur = 25 / sk * (10.f + 1.2f * stru);

    rtengine::guidedFilter(guid, blend2, ble, blur, 0.001, multiThread);

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            blend2[ir][jr] = 32768.f * ble[ir][jr];
        }
    }
}


static void blendmask(const local_params& lp, int xstart, int ystart, int cx, int cy, int bfw, int bfh, LabImage* bufexporig, LabImage* original, LabImage* bufmaskor, LabImage* originalmas, float bl, float blab, int inv)
{
    bl /= 10.f;
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16)
#endif

    for (int y = 0; y < bfh ; y++) {
        const int loy = y + ystart + cy;

        for (int x = 0; x < bfw; x++) {
            const int lox = x + xstart + cx;
            int zone;

            float localFactor = 1.f;
            float achm = lp.trans / 100.f;
            if(lp.fullim == 3 ) {//disable transit
                achm = 1.f;
            }

            if (lp.shapmet == 0) {
                calcTransition(lox, loy, achm, lp, zone, localFactor);
            } else { /*if (lp.shapmet == 1)*/
                calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
            }
            if(lp.fullim == 3 ) {//disable scope
                localFactor = 1.f;
            }

            if (inv == 0) {
                if (zone > 0) {
                    bufexporig->L[y][x] += (bl * bufmaskor->L[y][x]);
                    bufexporig->a[y][x] *= (1.f + blab * bufmaskor->a[y][x]);
                    bufexporig->b[y][x] *= (1.f + blab * bufmaskor->b[y][x]);

                    bufexporig->L[y][x] = CLIP(bufexporig->L[y][x]);
                    bufexporig->a[y][x] = clipC(bufexporig->a[y][x]);
                    bufexporig->b[y][x] = clipC(bufexporig->b[y][x]);

                    originalmas->L[y][x] = CLIP(bufexporig->L[y][x] - bufmaskor->L[y][x]);
                    originalmas->a[y][x] = clipC(bufexporig->a[y][x] * (1.f - bufmaskor->a[y][x]));
                    originalmas->b[y][x] = clipC(bufexporig->b[y][x] * (1.f - bufmaskor->b[y][x]));

                    original->L[y + ystart][x + xstart] += (bl * localFactor * bufmaskor->L[y][x]);
                    original->a[y + ystart][x + xstart] *= (1.f + blab * localFactor * bufmaskor->a[y][x]);
                    original->b[y + ystart][x + xstart] *= (1.f + blab * localFactor * bufmaskor->b[y][x]);
                    original->L[y + ystart][x + xstart] = CLIP(original->L[y + ystart][x + xstart]);
                    original->a[y + ystart][x + xstart] = clipC(original->a[y + ystart][x + xstart]);
                    original->b[y + ystart][x + xstart] = clipC(original->b[y + ystart][x + xstart]);

                }
            } else if (inv == 1) {
                localFactor = 1.f - localFactor;

                if (zone < 2) {
                    bufexporig->L[y][x] += (bl * bufmaskor->L[y][x]);
                    bufexporig->a[y][x] *= (1.f + blab * bufmaskor->a[y][x]);
                    bufexporig->b[y][x] *= (1.f + blab * bufmaskor->b[y][x]);

                    bufexporig->L[y][x] = CLIP(bufexporig->L[y][x]);
                    bufexporig->a[y][x] = clipC(bufexporig->a[y][x]);
                    bufexporig->b[y][x] = clipC(bufexporig->b[y][x]);

                    originalmas->L[y][x] = CLIP(bufexporig->L[y][x] - bufmaskor->L[y][x]);
                    originalmas->a[y][x] = clipC(bufexporig->a[y][x] * (1.f - bufmaskor->a[y][x]));
                    originalmas->b[y][x] = clipC(bufexporig->b[y][x] * (1.f - bufmaskor->b[y][x]));

                    switch (zone) {
                        case 0: {
                            original->L[y + ystart][x + xstart] += (bl * bufmaskor->L[y][x]);
                            original->a[y + ystart][x + xstart] *= (1.f + blab * bufmaskor->a[y][x]);
                            original->b[y + ystart][x + xstart] *= (1.f + blab * bufmaskor->b[y][x]);
                            original->L[y + ystart][x + xstart] = CLIP(original->L[y + ystart][x + xstart]);
                            original->a[y + ystart][x + xstart] = clipC(original->a[y + ystart][x + xstart]);
                            original->b[y + ystart][x + xstart] = clipC(original->b[y + ystart][x + xstart]);
                            break;
                        }

                        case 1: {
                            original->L[y + ystart][x + xstart] += (bl * localFactor * bufmaskor->L[y][x]);
                            original->a[y + ystart][x + xstart] *= (1.f + blab * localFactor * bufmaskor->a[y][x]);
                            original->b[y + ystart][x + xstart] *= (1.f + blab * localFactor * bufmaskor->b[y][x]);
                            original->L[y + ystart][x + xstart] = CLIP(original->L[y + ystart][x + xstart]);
                            original->a[y + ystart][x + xstart] = clipC(original->a[y + ystart][x + xstart]);
                            original->b[y + ystart][x + xstart] = clipC(original->b[y + ystart][x + xstart]);
                        }

                    }
                }

            }
        }
    }
}

void ImProcFunctions::deltaEforMask(float **rdE, int bfw, int bfh, LabImage* bufcolorig, const float hueref, const float chromaref, const float lumaref,
                                    float maxdE, float mindE, float maxdElim,  float mindElim, float iterat, float limscope, int scope, float balance, float balanceh)
{
    const float refa = chromaref * cos(hueref);
    const float refb = chromaref * sin(hueref);
    const float refL = lumaref;

    const float kL = balance;
    const float kab = balancedeltaE(kL);
    const float kH = balanceh;
    const float kch = balancedeltaE(kH);

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            const float abdelta2 = SQR(refa - bufcolorig->a[y][x] / 327.68f) + SQR(refb - bufcolorig->b[y][x] / 327.68f);
            const float chrodelta2 = SQR(std::sqrt(SQR(bufcolorig->a[y][x]) + SQR(bufcolorig->b[y][x])) / 327.68f - chromaref);
            const float huedelta2 = abdelta2 - chrodelta2;
            const float tempdE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - bufcolorig->L[y][x] / 327.68f));

            float reducdE;

            if (tempdE > maxdE) {
                reducdE = 0.f;
            } else if (tempdE > mindE && tempdE <= maxdE) {
                const float ar = 1.f / (mindE - maxdE);
                const float br = - ar * maxdE;
                reducdE = pow(ar * tempdE + br, iterat);
            } else {
                reducdE = 1.f;
            }

            if (scope > limscope) {
                if (tempdE > maxdElim) {
                    reducdE = 0.f;
                } else if (tempdE > mindElim && tempdE <= maxdElim) {
                    const float arlim = 1.f / (mindElim - maxdElim);
                    const float brlim = - arlim * maxdElim;
                    const float reducdElim = pow(arlim * tempdE + brlim, iterat);
                    const float aalim = (1.f - reducdElim) / 20.f;
                    const float bblim = 1.f - 100.f * aalim;
                    reducdE = aalim * scope + bblim;
                } else {
                    reducdE = 1.f;
                }
            }

            rdE[y][x] = reducdE ;
        }
    }
}

static void showmask(int lumask, const local_params& lp, int xstart, int ystart, int cx, int cy, int bfw, int bfh, LabImage* bufexporig, LabImage* transformed, LabImage* bufmaskorigSH, int inv)
{
    if(lp.fullim == 3) {
        return;
    }
    float lum = fabs(lumask * 400.f);
    float colo = 0.f;

    if (lumask < 0.f) {
        lum *= 1.4f;
        colo = 30000.f + 12.f * lum;
    }

#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16)
#endif

    for (int y = 0; y < bfh; y++) {
        const int loy = y + ystart + cy;

        for (int x = 0; x < bfw; x++) {
            const int lox = x + xstart + cx;
            int zone;
            float localFactor = 1.f;
            const float achm = lp.trans / 100.f;

            if (lp.shapmet == 0) {
                calcTransition(lox, loy, achm, lp, zone, localFactor);
            } else { /*if (lp.shapmet == 1)*/
                calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
            }

            if (inv == 0) {
                if (zone > 0) {//normal
                    transformed->L[y + ystart][x + xstart] = (lum) + clipLoc(bufmaskorigSH->L[y][x]);
                    transformed->a[y + ystart][x + xstart] = bufexporig->a[y][x] * bufmaskorigSH->a[y][x];
                    transformed->b[y + ystart][x + xstart] = (colo) + bufexporig->b[y][x] * bufmaskorigSH->b[y][x];
                }
            } else if (inv == 1) { //inverse
                if (zone == 0) {
                    transformed->L[y + ystart][x + xstart] = (lum) + clipLoc(bufmaskorigSH->L[y][x]);
                    transformed->a[y + ystart][x + xstart] = bufexporig->a[y][x] * bufmaskorigSH->a[y][x];
                    transformed->b[y + ystart][x + xstart] = (colo) + bufexporig->b[y][x] * bufmaskorigSH->b[y][x];
                }
            }

        }
    }
}
//from A.Griggio...very similar to discrete_laplacian_threhold...some differences with ceiling and data format
void ImProcFunctions::laplacian(const array2D<float> &src, array2D<float> &dst, int bfw, int bfh, float threshold, float ceiling, float factor, bool multiThread)
{
    const int W = bfw;
    const int H = bfh;

    const auto X =
    [W](int x) -> int {
        return x < 0 ? x + 2 : (x >= W ? x - 2 : x);
    };

    const auto Y =
    [H](int y) -> int {
        return y < 0 ? y + 2 : (y >= H ? y - 2 : y);
    };

    const auto get =
    [&src](int y, int x) -> float {
        return std::max(src[y][x], 0.f);
    };

    dst(W, H);
    const float f = factor / ceiling;

#ifdef _OPENMP
    #   pragma omp parallel for if (multiThread)
#endif

    for (int y = 0; y < H; ++y) {
        int n = Y(y - 1), s = Y(y + 1);

        for (int x = 0; x < W; ++x) {
            int w = X(x - 1), e = X(x + 1);
            float v = -8.f * get(y, x) + get(n, x) + get(s, x) + get(y, w) + get(y, e) + get(n, w) + get(n, e) + get(s, w) + get(s, e);
            dst[y][x] = LIM(std::abs(v) - threshold, 0.f, ceiling) * f;
        }
    }
}




void ImProcFunctions::discrete_laplacian_threshold(float * data_out, const float * data_in, size_t nx, size_t ny, float t)
{
    // BENCHFUN

    if (!data_in || !data_out) {
        fprintf(stderr, "a pointer is NULL and should not be so\n");
        abort();
    }

    /* pointers to the data and neighbour values */
    /*
     *                 y-1
     *             x-1 ptr x+1
     *                 y+1
     *    <---------------------nx------->
     */

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (size_t j = 0; j < ny; j++) {
        const float* ptr_in = &data_in[j * nx];
        float* ptr_out = &data_out[j * nx];

        for (size_t i = 0; i < nx; i++) {
            float val = 0.f;

            /* row differences */
            if (0 < i) {
                const float diff = ptr_in[i] - ptr_in[i - 1];
                val += std::fabs(diff) > t ? diff : 0.f;
            }

            if (nx - 1 > i) {
                const float diff = ptr_in[i] - ptr_in[i + 1];;
                val += std::fabs(diff) > t ? diff : 0.f;
            }

            /* column differences */
            if (0 < j) {
                const float diff = ptr_in[i] - ptr_in[i - nx];;
                val += std::fabs(diff) > t ? diff : 0.f;
            }

            if (ny - 1 > j) {
                const float diff = ptr_in[i] - ptr_in[i + nx];;
                val += std::fabs(diff) > t ? diff : 0.f;
            }

            ptr_out[i] = val;
        }
    }

}

float *ImProcFunctions::cos_table(size_t size)
{
    float *table = NULL;

    /* allocate the cosinus table */
    if (NULL == (table = (float *) malloc(sizeof(*table) * size))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    /*
     * fill the cosinus table,
     * table[i] = cos(i Pi / n) for i in [0..n[
     */
    const double pi_size = rtengine::RT_PI / size;

    for (size_t i = 0; i < size; i++) {
        table[i] = 1.0 - std::cos(pi_size * i);
    }

    return table;
}


void ImProcFunctions::rex_poisson_dct(float * data, size_t nx, size_t ny, double m)
{
    /*
     * Copyright 2009-2011 IPOL Image Processing On Line http://www.ipol.im/
     *

     * @file retinex_pde_lib.c discrete Poisson equation
     * @brief laplacian, DFT and Poisson routines
     *
     * @author Nicolas Limare <nicolas.limare@cmla.ens-cachan.fr>
     * some adaptations for Rawtherapee
     */
    // BENCHFUN

    /*
     * get the cosinus tables
     * cosx[i] = cos(i Pi / nx) for i in [0..nx[
     * cosy[i] = cos(i Pi / ny) for i in [0..ny[
     */

    float* cosx = cos_table(nx);
    float* cosy = cos_table(ny);

    /*
     * we will now multiply data[i, j] by
     * m / (4 - 2 * cosx[i] - 2 * cosy[j]))
     * and set data[0, 0] to 0
     */
    float m2 = m / 2.;
    /*
     * after that, by construction, we always have
     * cosx[] + cosy[] != 2.
    */

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (size_t i = 0; i < ny; ++i) {
        for (size_t j = 0; j < nx; ++j) {
            data[i * nx + j] *= m2 / (cosx[j] + cosy[i]);
        }
    }

    // handle the first value, data[0, 0] = 0
    data[0] = 0.f;

    free(cosx);
    free(cosy);

}

void ImProcFunctions::mean_dt(const float* data, int size, double& mean_p, double& dt_p, double nbstd)
{

    double mean = 0.;
    double dt = 0.;

#ifdef _OPENMP
    #pragma omp parallel for reduction(+:mean,dt) if(multiThread)
#endif

    for (int i = 0; i < size; i++) {
        mean += static_cast<double>(data[i]);
        dt += static_cast<double>(SQR(data[i]));
    }

    mean /= size;
    dt /= size;
    dt -= SQR(mean);
    mean_p = mean;
    dt_p = nbstd * std::sqrt(dt);
}

void ImProcFunctions::normalize_mean_dt(float * data, const float * ref, int size, float mod, float sigm, float mdef, float sdef, float mdef2, float sdef2, double nbstd)
{
    /*
     * Copyright 2009-2011 IPOL Image Processing On Line http://www.ipol.im/
     *

     * @file retinex_pde_lib.c discrete Poisson equation
     * @brief laplacian, DFT and Poisson routines
     *
     * @author Nicolas Limare <nicolas.limare@cmla.ens-cachan.fr>
     * adapted for Rawtherapee - jacques Desmis july 2019 - march 2021
     */

    if (NULL == data || NULL == ref) {
        fprintf(stderr, "a pointer is NULL and should not be so\n");
        abort();
    }

    double mean_ref, mean_data, dt_ref, dt_data;

    /* compute mean and variance of the two arrays */
    if (mdef != 0.f && sdef != 0.f) {
        mean_ref = mdef;
        dt_ref = sdef;
    } else {
        mean_dt(ref, size, mean_ref, dt_ref, nbstd);
    }

    if (mdef2 != 0.f && sdef2 != 0.f) {
        mean_data = mdef2;
        dt_data = sdef2;
    } else {
        mean_dt(data, size, mean_data, dt_data, 1.0);
    }

    /* compute the normalization coefficients */
    const double a = dt_ref / dt_data;
    const double b = mean_ref - a * mean_data;

    const float modma = static_cast<double>(mod) * a;
    const float sigmmmodmb = static_cast<double>(sigm) * static_cast<double>(mod) * b;
    const float onesmod = 1.f - mod;
    /* normalize the array */

#ifdef _OPENMP
    #pragma omp parallel for if(multiThread)
#endif

    for (int i = 0; i < size; i++) {
        data[i] = (modma * data[i] + sigmmmodmb) + onesmod * ref[i];//normalize mean and stdv and balance PDE
    }

}

void ImProcFunctions::retinex_pde(const float * datain, float * dataout, int bfw, int bfh, float thresh, float multy, float * dE, int show, int dEenable, int normalize)
{
    /*
     * Copyright 2009-2011 IPOL Image Processing On Line http://www.ipol.im/
     *

     * @file retinex_pde_lib.c discrete Poisson equation
     * @brief laplacian, DFT and Poisson routines
     *
     * @author Nicolas Limare <nicolas.limare@cmla.ens-cachan.fr>
     * adapted for Rawtherapee by Jacques Desmis 6-2019 <jdesmis@gmail.com>
     */

    // BENCHFUN

#ifdef RT_FFTW3F_OMP
    if (multiThread) {
        fftwf_init_threads();
        fftwf_plan_with_nthreads(omp_get_max_threads());
    }

#endif

    float *datashow = nullptr;

    if (show != 0) {
        datashow = (float *) fftwf_malloc(sizeof(float) * bfw * bfh);

        if (!datashow) {
            fprintf(stderr, "allocation error\n");
            abort();
        }
    }

    float *data_tmp = (float *) fftwf_malloc(sizeof(float) * bfw * bfh);

    if (!data_tmp) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    //first call to laplacian with plein strength
    discrete_laplacian_threshold(data_tmp, datain, bfw, bfh, thresh);

    float *data_fft = (float *) fftwf_malloc(sizeof(float) * bfw * bfh);

    if (!data_fft) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    if (show == 1) {
        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                datashow[y * bfw + x] = data_tmp[y * bfw + x];
            }
        }
    }

    //execute first
    const auto dct_fw = fftwf_plan_r2r_2d(bfh, bfw, data_tmp, data_fft, FFTW_REDFT10, FFTW_REDFT10, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
    fftwf_execute(dct_fw);
    fftwf_destroy_plan(dct_fw);

    //execute second
    if (dEenable == 1) {
        float* data_fft04 = (float *)fftwf_malloc(sizeof(float) * bfw * bfh);
        float* data_tmp04 = (float *)fftwf_malloc(sizeof(float) * bfw * bfh);

        if (!data_fft04 || !data_tmp04) {
            fprintf(stderr, "allocation error\n");
            abort();
        }

        //second call to laplacian with 40% strength ==> reduce effect if we are far from ref (deltaE)
        discrete_laplacian_threshold(data_tmp04, datain, bfw, bfh, 0.4f * thresh);
        const auto dct_fw04 = fftwf_plan_r2r_2d(bfh, bfw, data_tmp04, data_fft04, FFTW_REDFT10, FFTW_REDFT10, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
        fftwf_execute(dct_fw04);
        fftwf_destroy_plan(dct_fw04);
        constexpr float exponent = 4.5f;

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
#if defined(__SSE2__) || defined(RT_SIMDE)
            const vfloat exponentv = F2V(exponent);
#endif
#ifdef _OPENMP
            #pragma omp for
#endif

            for (int y = 0; y < bfh ; y++) {//mix two fftw Laplacian : plein if dE near ref
                int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                for (; x < bfw - 3; x += 4) {
                    STVFU(data_fft[y * bfw + x], intp(pow_F(LVFU(dE[y * bfw + x]), exponentv), LVFU(data_fft[y * bfw + x]), LVFU(data_fft04[y * bfw + x])));
                }

#endif

                for (; x < bfw; x++) {
                    data_fft[y * bfw + x] = intp(pow_F(dE[y * bfw + x], exponent), data_fft[y * bfw + x], data_fft04[y * bfw + x]);
                }
            }
        }
        fftwf_free(data_fft04);
        fftwf_free(data_tmp04);
    }

    if (show == 2) {
        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                datashow[y * bfw + x] = data_fft[y * bfw + x];
            }
        }
    }

    /* solve the Poisson PDE in Fourier space */
    /* 1. / (float) (bfw * bfh)) is the DCT normalisation term, see libfftw */
    rex_poisson_dct(data_fft, bfw, bfh, 1. / (double)(bfw * bfh));

    if (show == 3) {
        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                datashow[y * bfw + x] = data_fft[y * bfw + x];
            }
        }
    }

    const auto dct_bw = fftwf_plan_r2r_2d(bfh, bfw, data_fft, data_tmp, FFTW_REDFT01, FFTW_REDFT01, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
    fftwf_execute(dct_bw);
    fftwf_destroy_plan(dct_bw);
    fftwf_free(data_fft);

    if (show != 4 && normalize == 1) {
        normalize_mean_dt(data_tmp, datain, bfw * bfh, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
    }

    if (show == 0 || show == 4) {

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                dataout[y * bfw + x] = clipLoc(multy * data_tmp[y * bfw + x]);
            }
        }
    } else if (show == 1 || show == 2 || show == 3) {
        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                dataout[y * bfw + x] = clipLoc(multy * datashow[y * bfw + x]);
            }
        }
    }

    fftwf_free(data_tmp);

    if (datashow) {
        fftwf_free(datashow);
    }

    fftwf_cleanup();

#ifdef RT_FFTW3F_OMP

    if (multiThread) {
        fftwf_cleanup_threads();
    }

#endif
}

void ImProcFunctions::maskcalccol(int call, bool invmask, bool pde, int bfw, int bfh, int oW, int oH, int tX, int tY, int tW, int tH, int xstart, int ystart, int xend, int yend, int sk, int cx, int cy, LabImage* bufcolorig, LabImage* bufmaskblurcol, LabImage* originalmaskcol, LabImage* original, LabImage* reserved, int inv, struct local_params & lp,
                                  float strumask, bool astool,
                                  const LocCCmaskCurve & locccmasCurve, bool lcmasutili,
                                  const LocLLmaskCurve & locllmasCurve, bool llmasutili,
                                  const LocHHmaskCurve & lochhmasCurve, bool lhmasutili, const LocHHmaskCurve & lochhhmasCurve, bool lhhmasutili,
                                  bool multiThread, bool enaMask, bool showmaske, bool deltaE, bool modmask, bool zero, bool modif, float chrom, float rad, float lap, float gamma, float slope, float blendm, float blendmab, int shado, int highl, float amountcd, float anchorcd,
                                  const LUTf& lmasklocalcurve, bool localmaskutili,
                                  const LocwavCurve & loclmasCurvecolwav, bool lmasutilicolwav, int level_bl, int level_hl, int level_br, int level_hr,
                                  int shortcu, bool delt, const float hueref, const float chromaref, const float lumaref,
                                  float maxdE, float mindE, float maxdElim,  float mindElim, float iterat, float limscope, int scope,
                                  bool fftt, float blu_ma, float cont_ma, int indic, float &fab, int fw, int fh, float ksk, float kskx, float kbh, float kbw
                                 )


{
    if(lp.fullim == 3) {
        return;
    }
    array2D<float> ble(bfw, bfh);
    array2D<float> blechro(bfw, bfh);
    array2D<float> hue(bfw, bfh);
    array2D<float> guid(bfw, bfh);
    const std::unique_ptr<LabImage> bufreserv(new LabImage(bfw, bfh));
    float meanfab, corfab;
    float maxfab = -1000.f;
    float epsi = 0.001f;
    mean_fab(xstart, ystart, bfw, bfh, bufcolorig, 0, original, fab, meanfab, maxfab, chrom, multiThread);
    corfab = 0.7f * (65535.f) / (fab + epsi);//empirical values 0.7 link to chromult

    float chromult = 1.f;

    if (chrom > 0.f) {
        chromult = 1.f + 0.003f * chrom;
    } else {
        chromult = 1.f + 0.01f * chrom;
    }

    // chromult * corfab * kmaskC
    float kinv = 1.f;
    float kneg = 1.f;

    if (invmask) {
        kinv = 0.f;
        kneg = -1.f;
    }

    if (deltaE || modmask || enaMask || showmaske) {
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < bfh; y++) {
            for (int x = 0; x < bfw; x++) {
                bufmaskblurcol->L[y][x] = original->L[y + ystart][x + xstart];
                bufmaskblurcol->a[y][x] = original->a[y + ystart][x + xstart];
                bufmaskblurcol->b[y][x] = original->b[y + ystart][x + xstart];
                bufreserv->L[y][x] = reserved->L[y + ystart][x + xstart];
                bufreserv->a[y][x] = reserved->a[y + ystart][x + xstart];
                bufreserv->b[y][x] = reserved->b[y + ystart][x + xstart];
            }
        }

        JaggedArray<float> blendstru(bfw, bfh);

        if (blu_ma >= 0.25f && strumask == 0.f) {
            strumask = 0.1f; // to enable a small mask make FFT good ...why ??
        }

        if (strumask > 0.f) {
            float delstrumask = 4.1f - strumask;//4.1 = 2 * max slider strumask + 0.1
            buildBlendMask(bufcolorig->L, blendstru, bfw, bfh, delstrumask);
            float radblur = 0.02f * std::fabs(0.1f * rad);//empirical value
            float rm = radblur / sk;

            if (rm > 0) {
                float **mb = blendstru;
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
                    gaussianBlur(mb, mb, bfw, bfh, rm);
                }
            }

        }

        JaggedArray<float> blendblur(bfw, bfh);

        JaggedArray<float> blur(bfw, bfh);

        if (cont_ma > 0.f) {
            float contra = cont_ma;
            buildBlendMask(bufcolorig->L, blendblur, bfw, bfh, contra);


            float radblur = 0.25f + 0.002f * std::fabs(rad);//empirical value
            float rm = radblur / sk;

            if (fftt) {
                if (rm < 0.3f) {
                    rm = 0.3f;
                }
            }

            if (rm > 0) {
                float **mb = blendblur;
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
                    gaussianBlur(mb, mb, bfw, bfh, rm);
                }
            }

            if (blu_ma >= 0.25f) {
                if (!fftt) { // || (lp.fftColorMask && call != 2)) {
#ifdef _OPENMP
                    #pragma omp parallel if (multiThread)
#endif
                    {
                        gaussianBlur(bufcolorig->L, blur, bfw, bfh, blu_ma / sk);
                    }
                } else {
                    ImProcFunctions::fftw_convol_blur2(bufcolorig->L, blur, bfw, bfh, blu_ma / sk, 0, 0);
                }

                for (int i = 0; i < bfh; i++) {
                    for (int j = 0; j < bfw; j++) {
                        blur[i][j] = intp(blendblur[i][j], bufcolorig->L[i][j], rtengine::max(blur[i][j], 0.0f));
                    }
                }
            }
        }

        bool HHmaskcurve = false;

        if (lochhhmasCurve && lhhmasutili) {
            for (int i = 0; i < 500; i++) {
                if (lochhhmasCurve[i] != 0.5f) {
                    HHmaskcurve = true;
                    break;
                }
            }
        }

//denoise mask chroma


        LabImage tmpab(bfw, bfh);
        tmpab.clear(true);

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int ir = 0; ir < bfh; ir++)
            for (int jr = 0; jr < bfw; jr++) {
                tmpab.L[ir][jr] = bufcolorig->L[ir][jr];
                tmpab.a[ir][jr] = bufcolorig->a[ir][jr];
                tmpab.b[ir][jr] = bufcolorig->b[ir][jr];
            }

        float noisevarab_r = SQR(lp.denoichmask / 10.f);

        if (noisevarab_r > 0.f) {
            int wavelet_leve = 6;

            int minwin1 = rtengine::min(bfw, bfh);
            int maxlevelspot1 = 9;

            while ((1 << maxlevelspot1) >= (minwin1) && maxlevelspot1  > 1) {
                --maxlevelspot1 ;
            }

            wavelet_leve = rtengine::min(wavelet_leve, maxlevelspot1);
            int maxlvl1 = wavelet_leve;
#ifdef _OPENMP
            const int numThreads = omp_get_max_threads();
#else
            const int numThreads = 1;

#endif

            wavelet_decomposition Ldecomp(tmpab.L[0], tmpab.W, tmpab.H, maxlvl1, 1, sk, numThreads, lp.daubLen);
            wavelet_decomposition adecomp(tmpab.a[0], tmpab.W, tmpab.H, maxlvl1, 1, sk, numThreads, lp.daubLen);
            wavelet_decomposition bdecomp(tmpab.b[0], tmpab.W, tmpab.H, maxlvl1, 1, sk, numThreads, lp.daubLen);
            float* noisevarchrom;
            noisevarchrom = new float[bfw * bfh];
            float nvch = 0.6f;//high value
            float nvcl = 0.1f;//low value
            float seuil = 4000.f;//low
            float seuil2 = 15000.f;//high
            //ac and bc for transition
            float ac = (nvch - nvcl) / (seuil - seuil2);
            float bc = nvch - seuil * ac;
            int bfw2 = (bfw + 1) / 2;

#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    float cN = std::sqrt(SQR(tmpab.a[ir][jr]) + SQR(tmpab.b[ir][jr]));

                    if (cN < seuil) {
                        noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] =  nvch;
                    } else if (cN < seuil2) {
                        noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] = ac * cN + bc;
                    } else {
                        noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] =  nvcl;
                    }
                }

            float madL[8][3];
            int levred = maxlvl1;

            if (!Ldecomp.memory_allocation_failed()) {
#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic) collapse(2) if (multiThread)
#endif

                for (int lvl = 0; lvl < levred; lvl++) {
                    for (int dir = 1; dir < 4; dir++) {
                        int Wlvl_L = Ldecomp.level_W(lvl);
                        int Hlvl_L = Ldecomp.level_H(lvl);
                        const float* const* WavCoeffs_L = Ldecomp.level_coeffs(lvl);

                        madL[lvl][dir - 1] = SQR(Mad(WavCoeffs_L[dir], Wlvl_L * Hlvl_L));
                    }
                }
            }

            if (!adecomp.memory_allocation_failed() && !bdecomp.memory_allocation_failed()) {
                WaveletDenoiseAll_BiShrinkAB(Ldecomp, adecomp, noisevarchrom, madL, nullptr, 0, noisevarab_r, true, false, false, numThreads);
                WaveletDenoiseAllAB(Ldecomp, adecomp, noisevarchrom, madL, nullptr, 0, noisevarab_r, true, false, false, numThreads);

                WaveletDenoiseAll_BiShrinkAB(Ldecomp, bdecomp, noisevarchrom, madL, nullptr, 0, noisevarab_r, false, false, false, numThreads);
                WaveletDenoiseAllAB(Ldecomp, bdecomp, noisevarchrom, madL, nullptr, 0, noisevarab_r, false, false, false, numThreads);

            }

            delete[] noisevarchrom;

            if (!Ldecomp.memory_allocation_failed()) {
                Ldecomp.reconstruct(tmpab.L[0]);
            }

            if (!adecomp.memory_allocation_failed()) {
                adecomp.reconstruct(tmpab.a[0]);
            }

            if (!bdecomp.memory_allocation_failed()) {
                bdecomp.reconstruct(tmpab.b[0]);
            }

            float meanfab1, fab1;
            float maxfab1 = 0.f;
            std::unique_ptr<LabImage> buforig;
            buforig.reset(new LabImage(bfw, bfh));
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    buforig->L[ir][jr] = tmpab.L[ir][jr];
                    buforig->a[ir][jr] = tmpab.a[ir][jr];
                    buforig->b[ir][jr] = tmpab.b[ir][jr];
                }


            mean_fab(xstart, ystart, bfw, bfh, buforig.get(), 1, buforig.get(), fab1, meanfab1, maxfab1, chrom, multiThread);
            fab = fab1;//fab denoise

        }

// end code denoise mask chroma




#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
#if defined(__SSE2__) || defined(RT_SIMDE)
            float atan2Buffer[bfw] ALIGNED64;
//            float atan2BufferH[bfw] ALIGNED64;
#endif
#ifdef _OPENMP
            #pragma omp for schedule(dynamic, 16)
#endif

            for (int ir = 0; ir < bfh; ir++) {
#if defined(__SSE2__) || defined(RT_SIMDE)

                if (lochhmasCurve && lhmasutili) {
                    int i = 0;

                    for (; i < bfw - 3; i += 4) {
                        //   STVF(atan2Buffer[i], xatan2f(LVFU(bufcolorig->b[ir][i]), LVFU(bufcolorig->a[ir][i])));
                        STVF(atan2Buffer[i], xatan2f(LVFU(tmpab.b[ir][i]), LVFU(tmpab.a[ir][i])));
                    }

                    for (; i < bfw; i++) {
                        //  atan2Buffer[i] = xatan2f(bufcolorig->b[ir][i], bufcolorig->a[ir][i]);
                        atan2Buffer[i] = xatan2f(tmpab.b[ir][i], tmpab.a[ir][i]);
                    }
                }

#endif

                for (int jr = 0; jr < bfw; jr++) {
                    float kmaskL = 0.f;
                    float kmaskC = 0.f;
                    float kmaskHL = 0.f;
                    float kmaskH = 0.f;
                    float kmasstru = 0.f;
                    float kmasblur = 0.f;

                    if (strumask > 0.f && !astool) {
                        kmasstru = bufcolorig->L[ir][jr] * blendstru[ir][jr];
                    }

                    if (cont_ma > 0.f) {

                        if (blu_ma >= 0.25f) {

                            float prov = intp(blendstru[ir][jr], bufcolorig->L[ir][jr], rtengine::max(blur[ir][jr], 0.0f));
                            kmasblur = bufcolorig->L[ir][jr] - prov ;

                        }
                    }

                    if (locllmasCurve && llmasutili) {
                        kmaskL = 32768.f * LIM01(kinv - kneg * locllmasCurve[(500.f / 32768.f) * bufcolorig->L[ir][jr]]);

                    }

                    if (!deltaE && locccmasCurve && lcmasutili) {
                        //  kmaskC = LIM01(kinv  - kneg * locccmasCurve[500.f * (0.0001f + std::sqrt(SQR(bufcolorig->a[ir][jr]) + SQR(bufcolorig->b[ir][jr])) / (fab))]);
                        kmaskC = LIM01(kinv  - kneg * locccmasCurve[500.f * (0.0001f + std::sqrt(SQR(tmpab.a[ir][jr]) + SQR(tmpab.b[ir][jr])) / fab)]);
                    }

                    if (lochhmasCurve && lhmasutili) {
#if defined(__SSE2__) || defined(RT_SIMDE)
                        const float huema = atan2Buffer[jr];
#else
                        // const float huema = xatan2f(bufcolorig->b[ir][jr], bufcolorig->a[ir][jr]);
                        const float huema = xatan2f(tmpab.b[ir][jr], tmpab.a[ir][jr]);
#endif
                        float h = Color::huelab_to_huehsv2(huema);
                        h += 1.f / 6.f;

                        if (h > 1.f) {
                            h -= 1.f;
                        }

                        const float valHH = LIM01(kinv - kneg * lochhmasCurve[500.f *  h]);

                        if (!deltaE) {
                            kmaskH = valHH;
                        }

                        kmaskHL = 32768.f * valHH;
                    }

                    bufmaskblurcol->L[ir][jr] = clipLoc(kmaskL + kmaskHL + kmasstru + kmasblur);
                    bufmaskblurcol->a[ir][jr] = clipC((chromult * corfab * kmaskC + chromult * kmaskH));
                    bufmaskblurcol->b[ir][jr] = clipC((chromult * corfab * kmaskC + chromult * kmaskH));

                    if (shortcu == 1) { //short circuit all L curve
                        bufmaskblurcol->L[ir][jr] = 32768.f - bufcolorig->L[ir][jr];
                    }

                    ble[ir][jr] = bufmaskblurcol->L[ir][jr] / 32768.f;
                    hue[ir][jr] = xatan2f(bufmaskblurcol->b[ir][jr], bufmaskblurcol->a[ir][jr]);
                    const float chromah = std::sqrt(SQR(bufmaskblurcol->b[ir][jr]) + SQR(bufmaskblurcol->a[ir][jr]));

                    blechro[ir][jr] = chromah / 32768.f;//must be good perhaps more or less, only incidence on LIM blea bleb
                    guid[ir][jr] = Color::L2Y(bufcolorig->L[ir][jr]) / 32768.f;

                }
            }
        }

        if (lap > 0.f && pde) {
            array2D<float> mask;
            mask(bfw, bfh);
            float amount = LIM01(float(lap) / 100.f);
            array2D<float> LL(bfw, bfh, bufcolorig->L, ARRAY2D_BYREFERENCE);
            laplacian(LL, mask, bfw, bfh, 25.f, 20000.f, amount, false);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int i = 0; i < bfh; ++i) {
                for (int j = 0; j < bfw; ++j) {
                    mask[i][j] = LIM01(mask[i][j]);
                }
            }

            for (int i = 0; i < 3; ++i) {
                boxblur(static_cast<float**>(mask), static_cast<float**>(mask), 5 / sk, bfw, bfh, false);
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int i = 0; i < bfh; ++i) {
                for (int j = 0; j < bfw; ++j) {
                    bufmaskblurcol->L[i][j] += clipLoc(100000.f * (mask[i][j]));//increase strongly result
                }
            }
        }

        std::unique_ptr<LabImage> bufprov;

        if (delt) {
            bufprov.reset(new LabImage(bfw, bfh));
            bufprov->CopyFrom(bufmaskblurcol, multiThread);
        }

        if (rad != 0.f) {
            const float tmpblur = rad < 0.f ? -1.f / rad : 1.f + rad;
            const int r1 = rtengine::max<int>(4 / sk * tmpblur + 0.5f, 1);
            const int r2 = rtengine::max<int>(25 / sk * tmpblur + 0.5f, 1);

            constexpr float epsilmax = 0.005f;
            constexpr float epsilmin = 0.00001f;

            constexpr float aepsil = (epsilmax - epsilmin) / 100.f;
            constexpr float bepsil = epsilmin; //epsilmax - 100.f * aepsil;
            const float epsil = rad < 0.f ? 0.001f : aepsil * rad + bepsil;

            rtengine::guidedFilter(guid, blechro, blechro, r1, epsil, multiThread);
            rtengine::guidedFilter(guid, ble, ble, r2, 0.2f * epsil, multiThread);
        }

        LUTf lutTonemaskexp(65536);
        calcGammaLut(gamma, slope, lutTonemaskexp);

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int ir = 0; ir < bfh; ir++) {
            for (int jr = 0; jr < bfw; jr++) {
                float2 sincosval = xsincosf(hue[ir][jr]);
                bufmaskblurcol->L[ir][jr] = lutTonemaskexp[ble[ir][jr] * 65536.f];
                bufmaskblurcol->a[ir][jr] = 32768.f * blechro[ir][jr] * sincosval.y;
                bufmaskblurcol->b[ir][jr] = 32768.f * blechro[ir][jr] * sincosval.x;
            }
        }


        if (strumask > 0.f && astool) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    bufmaskblurcol->L[ir][jr] *= (1.f + blendstru[ir][jr]);
                }
            }
        }

        if (lmasklocalcurve && localmaskutili) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    bufmaskblurcol->L[ir][jr] = 0.5f * lmasklocalcurve[2.f * bufmaskblurcol->L[ir][jr]];
                }
        }

        if (shado > 0) {
            ImProcFunctions::shadowsHighlights(bufmaskblurcol, true, 1, 0, shado, 40, sk, 0, 60);
        }

        if (highl > 0) {
            ImProcFunctions::shadowsHighlights(bufmaskblurcol, true, 1, highl, 0, 40, sk, 50, 0);
        }

        int wavelet_level = level_br;

        int minwin = rtengine::min(bfw, bfh);
        int maxlevelspot = 9;

        while ((1 << maxlevelspot) >= (minwin) && maxlevelspot  > 1) {
            --maxlevelspot ;
        }

        wavelet_level = rtengine::min(wavelet_level, maxlevelspot);
        int maxlvl = wavelet_level;
//        float contrast = 0.f;
        bool wavcurvemask = false;

        if (loclmasCurvecolwav && lmasutilicolwav) {
            for (int i = 0; i < 500; i++) {
                if (loclmasCurvecolwav[i] != 0.5f) {
                    wavcurvemask = true;
                    break;
                }
            }
        }

        if (wavcurvemask) {
#ifdef _OPENMP
            const int numThreads = omp_get_max_threads();
#else
            const int numThreads = 1;

#endif
            wavelet_decomposition *wdspot = new wavelet_decomposition(bufmaskblurcol->L[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen);

            if (wdspot->memory_allocation_failed()) {
                return;
            }

            float mean[10];
            float meanN[10];
            float sigma[10];
            float sigmaN[10];
            float MaxP[10];
            float MaxN[10];
            Evaluate2(*wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);
            float alow = 1.f;
            float blow = 0.f;

            if (level_hl != level_bl) {
                alow = 1.f / (level_hl - level_bl);
                blow = -alow * level_bl;
            }

            float ahigh = 1.f;
            float bhigh = 0.f;

            if (level_hr != level_br) {
                ahigh = 1.f / (level_hr - level_br);
                bhigh =  -ahigh * level_br;
            }

            for (int dir = 1; dir < 4; dir++) {
                for (int level = level_bl; level < maxlvl; ++level) {
                    int W_L = wdspot->level_W(level);
                    int H_L = wdspot->level_H(level);
                    float* const* wav_L = wdspot->level_coeffs(level);

                    if (MaxP[level] > 0.f && mean[level] != 0.f && sigma[level] != 0.f) {
                        float insigma = 0.666f; //SD
                        float logmax = log(MaxP[level]); //log Max
                        float rapX = (mean[level] + sigma[level]) / MaxP[level]; //rapport between sD / max
                        float inx = log(insigma);
                        float iny = log(rapX);
                        float rap = inx / iny; //koef
                        float asig = 0.166f / (sigma[level]);
                        float bsig = 0.5f - asig * mean[level];
                        float amean = 0.5f / mean[level];

#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int i = 0; i < W_L * H_L; i++) {
                            if (loclmasCurvecolwav && lmasutilicolwav) {
                                float absciss;
                                float &val = wav_L[dir][i];

                                if (fabsf(val) >= (mean[level] + sigma[level])) { //for max
                                    float valcour = xlogf(fabsf(val));
                                    float valc = valcour - logmax;
                                    float vald = valc * rap;
                                    absciss = xexpf(vald);
                                } else if (fabsf(val) >= mean[level]) {
                                    absciss = asig * fabsf(val) + bsig;
                                } else {
                                    absciss = amean * fabsf(val);
                                }

                                float klev = 1.f;

                                if (level >= level_hl && level <= level_hr) {
                                    klev = 1.f;
                                }

                                if (level_hl != level_bl) {
                                    if (level >= level_bl && level < level_hl) {
                                        klev = alow * level + blow;
                                    }
                                }

                                if (level_hr != level_br) {
                                    if (level > level_hr && level <= level_br) {
                                        klev = ahigh * level + bhigh;
                                    }
                                }

                                float kc = klev * (loclmasCurvecolwav[absciss * 500.f] - 0.5f);
                                float amplieffect = kc <= 0.f ? 1.f : 4.f;

                                float kinterm = 1.f + amplieffect * kc;
                                kinterm = kinterm <= 0.f ? 0.01f : kinterm;

                                val *=  kinterm;

                            }
                        }
                    }

                }
            }

            wdspot->reconstruct(bufmaskblurcol->L[0], 1.f);
            delete wdspot;

        }

        if (lochhhmasCurve && HHmaskcurve) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++)
                for (int jr = 0; jr < bfw; jr++) {
                    float huemah = xatan2f(bufmaskblurcol->b[ir][jr], bufmaskblurcol->a[ir][jr]);
                    float chromah = std::sqrt(SQR(bufmaskblurcol->b[ir][jr]) + SQR(bufmaskblurcol->a[ir][jr]));


                    float hh = Color::huelab_to_huehsv2(huemah);
                    hh += 1.f / 6.f;

                    if (hh > 1.f) {
                        hh -= 1.f;
                    }

                    const float val_HH = float ((0.5f - lochhhmasCurve[500.f * hh]));
                    const float hhro = 1.5f * val_HH;
                    float newhr = 0.f;

                    if (hhro != 0) {
                        newhr = huemah + hhro;//we add radians and other dim between 0 1.. always radians but addition "false"

                        if (newhr > rtengine::RT_PI_F) {
                            newhr -= 2 * rtengine::RT_PI_F;
                        } else if (newhr < -rtengine::RT_PI_F) {
                            newhr += 2 * rtengine::RT_PI_F;
                        }
                    }

                    float2 sincosval = xsincosf(newhr);
                    bufmaskblurcol->a[ir][jr] = clipC(chromah * sincosval.y);
                    bufmaskblurcol->b[ir][jr] = clipC(chromah * sincosval.x);

                }
        }

        if (amountcd > 1.f) { //dynamic range compression for Mask
            FattalToneMappingParams fatParams;
            fatParams.enabled = true;
            fatParams.threshold = 100.f;
            fatParams.amount = amountcd;
            fatParams.anchor = anchorcd;
            int nlev = 1;
            Imagefloat *tmpImagefat = nullptr;
            tmpImagefat = new Imagefloat(bfw, bfh);
            lab2rgb(*bufmaskblurcol, *tmpImagefat, params->icm.workingProfile);
            ToneMapFattal02(tmpImagefat, fatParams, nlev, 0, nullptr, 0, 0, 0, false);
            rgb2lab(*tmpImagefat, *bufmaskblurcol, params->icm.workingProfile);
            delete tmpImagefat;
        }

        if (delt) {
            const std::unique_ptr<JaggedArray<float>> rdEBuffer(new JaggedArray<float>(bfw, bfh));
            float** rdE = *rdEBuffer;

            deltaEforMask(rdE, bfw, bfh, bufreserv.get(), hueref, chromaref, lumaref, maxdE, mindE, maxdElim, mindElim, iterat, limscope, scope, lp.balance, lp.balanceh);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    const float rdEval = rdE[ir][jr];
                    bufmaskblurcol->L[ir][jr] = bufprov->L[ir][jr] + rdEval * (bufmaskblurcol->L[ir][jr] - bufprov->L[ir][jr]);
                    bufmaskblurcol->a[ir][jr] = bufprov->a[ir][jr] + rdEval * (bufmaskblurcol->a[ir][jr] - bufprov->a[ir][jr]);
                    bufmaskblurcol->b[ir][jr] = bufprov->b[ir][jr] + rdEval * (bufmaskblurcol->b[ir][jr] - bufprov->b[ir][jr]);
                }
            }
        }

        struct grad_params gp;

        if ((indic == 0 && lp.strmaexp != 0.f) || (indic == 12 &&  lp.str_mas != 0.f)) {
            calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, indic, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < bfh; ir++) {
                for (int jr = 0; jr < bfw; jr++) {
                    bufmaskblurcol->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                }
            }
        }

        /*
                if (lap > 0.f) {
                    const float *datain = bufmaskblurcol->L[0];
                    const std::unique_ptr<float[]> data_tmp(new float[bfh * bfw]);

                    if (!pde) {
                        ImProcFunctions::discrete_laplacian_threshold(data_tmp.get(), datain, bfw, bfh, 200.f * lap);
                    } else {
                        ImProcFunctions::retinex_pde(datain, data_tmp.get(), bfw, bfh, 12.f * lap, 1.f, nullptr, 0, 0, 1);
                    }

        #ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
        #endif
                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            bufmaskblurcol->L[y][x] = data_tmp[y * bfw + x];
                        }
                    }
                }
                */
    }

    const float radiusb = 1.f / sk;

    if (deltaE || modmask || enaMask || showmaske) {
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(bufmaskblurcol->L, bufmaskblurcol->L, bfw, bfh, radiusb);
            gaussianBlur(bufmaskblurcol->a, bufmaskblurcol->a, bfw, bfh, 1.f + (0.5f * rad) / sk);
            gaussianBlur(bufmaskblurcol->b, bufmaskblurcol->b, bfw, bfh, 1.f + (0.5f * rad) / sk);
        }

        if (zero || modif || modmask || deltaE || enaMask) {
            originalmaskcol->CopyFrom(bufcolorig, multiThread);
            blendmask(lp, xstart, ystart, cx, cy, bfw, bfh, bufcolorig, original, bufmaskblurcol, originalmaskcol, blendm, blendmab, inv);
        }
    }
}

void ImProcFunctions::InverseSharp_Local(float **loctemp, const float hueref, const float lumaref, const float chromaref, local_params & lp, LabImage * original, LabImage * transformed, int cx, int cy, int sk)
{
//local sharp
    //  BENCHFUN
    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disable transit
        ach = 1.f;
    }
    
    const int GW = transformed->W;
    const int GH = transformed->H;
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;
    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);
    const bool sharshow = lp.showmasksharmet == 1;
    const bool previewshar = lp.showmasksharmet == 2;

    if (lp.colorde == 0) {
        lp.colorde = -1;//to avoid black
    }

    float ampli = 1.0 + std::fabs(lp.colorde);
    ampli = 2.f + 0.5f * (ampli - 2.f);

    constexpr float aadark = -1.f;
    constexpr float bbdark = 5000.f;

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));

    float radius = 3.f / sk;
#ifdef _OPENMP
    #pragma omp parallel
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const float mindE = 2.f + MINSCOPE * lp.senssha * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * lp.senssha * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            int loy = cy + y;

            for (int x = 0; x < transformed->W; x++) {
                int lox = cx + x;
                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }

                const float abdelta2 = SQR(refa - origblur->a[y][x]) + SQR(refb - origblur->b[y][x]);
                const float chrodelta2 = SQR(std::sqrt(SQR(origblur->a[y][x]) + SQR(origblur->b[y][x])) - (chromaref * 327.68f));
                const float huedelta2 = abdelta2 - chrodelta2;
                const float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - origblur->L[y][x]));

                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.senssha);
                if(lp.fullim == 3 ) {//disable scope
                    reducdE = 1.f;
                }

                switch (zone) {
                    case 0: { // outside selection and outside transition zone => full effect, no transition
                        const float difL = loctemp[y][x] - original->L[y][x];
                        transformed->L[y][x] = CLIP(original->L[y][x] + difL * reducdE);

                        if (sharshow && lp.fullim != 3) {
                            transformed->a[y][x] = 0.f;
                            transformed->b[y][x] = ampli * 5.f * difL * reducdE;
                        } else if (previewshar) {
                            float difbdisp = reducdE * 10000.f * lp.colorde;

                            if (transformed->L[y][x] < bbdark) { //enhance dark luminance as user can see!
                                float dark = transformed->L[y][x];
                                transformed->L[y][x] = dark * aadark + bbdark;
                            }

                            if (lp.colorde <= 0) {
                                transformed->a[y][x] = 0.f;
                                transformed->b[y][x] = difbdisp;
                            } else {
                                transformed->a[y][x] = -difbdisp;
                                transformed->b[y][x] = 0.f;
                            }

                        }

                        break;
                    }

                    case 1: { // inside transition zone
                        const float difL = (loctemp[y][x] - original->L[y][x]) * (1.f - localFactor);
                        transformed->L[y][x] = CLIP(original->L[y][x] + difL * reducdE);

                        if (sharshow && lp.fullim != 3) {
                            transformed->a[y][x] = 0.f;
                            transformed->b[y][x] = ampli * 5.f * difL * reducdE;
                        } else if (previewshar || lp.prevdE) {
                            const float difbdisp = reducdE * 10000.f * lp.colorde;

                            if (transformed->L[y][x] < bbdark) { //enhance dark luminance as user can see!
                                const float dark = transformed->L[y][x];
                                transformed->L[y][x] = dark * aadark + bbdark;
                            }

                            if (lp.colorde <= 0) {
                                transformed->a[y][x] = 0.f;
                                transformed->b[y][x] = difbdisp;
                            } else {
                                transformed->a[y][x] = -difbdisp;
                                transformed->b[y][x] = 0.f;
                            }
                        }

                        break;
                    }

                    case 2: { // inside selection => no effect, keep original values
                        transformed->L[y][x] = original->L[y][x];
                    }
                }
            }
        }
    }
}

void ImProcFunctions::Sharp_Local(int call, float **loctemp, int senstype, const float hueref, const float chromaref, const float lumaref, local_params & lp, LabImage * original, LabImage * transformed, int cx, int cy, int sk)
{
    //BENCHFUN
    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disable transit
        ach = 1.f;
    }
   
    const float varsens = senstype == 1 ? lp.senslc : lp.senssha;
    const bool sharshow = (lp.showmasksharmet == 1);
    const bool previewshar = (lp.showmasksharmet == 2);

    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);

    if (lp.colorde == 0) {
        lp.colorde = -1;//to avoid black
    }

    float ampli = 1.0 + std::fabs(lp.colorde);
    ampli = 2.f + 0.5f * (ampli - 2.f);

    float darklim = 5000.f;
    float aadark = -1.f;
    float bbdark = darklim;

    const int GW = transformed->W;
    const int GH = transformed->H;

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;
    const float radius = 3.f / sk;

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const int begy = int (lp.yc - lp.lyT);
        const int begx = int (lp.xc - lp.lxL);
        const float mindE = 2.f + MINSCOPE * varsens * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * varsens * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            const int loy = cy + y;
            const bool isZone0 = loy > lp.yc + lp.ly || loy < lp.yc - lp.lyT; // whole line is zone 0 => we can skip a lot of processing

            if (isZone0) { // outside selection and outside transition zone => no effect, keep original values
                continue;
            }

            for (int x = 0; x < transformed->W; x++) {
                const int lox = cx + x;
                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }

                if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

                //deltaE
                const float abdelta2 = SQR(refa - origblur->a[y][x]) + SQR(refb - origblur->b[y][x]);
                const float chrodelta2 = SQR(std::sqrt(SQR(origblur->a[y][x]) + SQR(origblur->b[y][x])) - (chromaref * 327.68f));
                const float huedelta2 = abdelta2 - chrodelta2;
                const float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - origblur->L[y][x]));

                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, varsens);
                float reducview = reducdE;
                reducdE *= localFactor;
                if(lp.fullim == 3 ) {//disable scope
                    reducview = reducdE = 1.f;
                }

                float difL;

                if (call == 2) {
                    difL = loctemp[loy - begy][lox - begx] - original->L[y][x];
                } else {
                    difL = loctemp[y][x] - original->L[y][x];
                }

                transformed->L[y][x] = CLIP(original->L[y][x] + difL * reducdE);

                if (sharshow && lp.fullim != 3) {
                    transformed->a[y][x] = 0.f;
                    transformed->b[y][x] = ampli * 5.f * difL * reducdE;
                } else if (previewshar || lp.prevdE) {
                    float difbdisp = reducview * 10000.f * lp.colorde;

                    if (transformed->L[y][x] < darklim) { //enhance dark luminance as user can see!
                        transformed->L[y][x] = transformed->L[y][x] * aadark + bbdark;
                    }

                    if (lp.colorde <= 0) {
                        transformed->a[y][x] = 0.f;
                        transformed->b[y][x] = difbdisp;
                    } else {
                        transformed->a[y][x] = -difbdisp;
                        transformed->b[y][x] = 0.f;
                    }
                }
            }
        }
    }
}

void ImProcFunctions::Exclude_Local(float **deltaso, float hueref, float chromaref, float lumaref, float sobelref, float meansobel, const struct local_params & lp, const LabImage * original, LabImage * transformed, const LabImage * rsv, const LabImage * reserv, int cx, int cy, int sk)
{

    // BENCHFUN
    {
        const float ach = lp.trans / 100.f;
        const float varsens =  lp.sensexclu;
        const float mindE = 2.f + MINSCOPE * varsens * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * varsens * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

        const int GW = transformed->W;
        const int GH = transformed->H;

        const float refa = chromaref * cos(hueref) * 327.68f;
        const float refb = chromaref * sin(hueref) * 327.68f;
        const float refL = lumaref * 327.68f;
        // lumaref *= 327.68f;
        //balance deltaE
        const float kL = lp.balance / SQR(327.68f);
        const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
        const float kH = lp.balanceh;
        const float kch = balancedeltaE(kH);
        //sobel
        sobelref = rtengine::min(sobelref / 100.f, 60.f);

        const bool recip = sobelref <  meansobel && sobelref < lp.stru;

        sobelref = log1p(sobelref);

        const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));

        const float radius = 3.f / sk;

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(reserv->L, origblur->L, GW, GH, radius);
            gaussianBlur(reserv->a, origblur->a, GW, GH, radius);
            gaussianBlur(reserv->b, origblur->b, GW, GH, radius);


#ifdef _OPENMP
            #pragma omp barrier
            #pragma omp for schedule(dynamic,16)
#endif

            for (int y = 0; y < transformed->H; y++) {
                const int loy = cy + y;
                const bool isZone0 = loy > (lp.yc + lp.ly - 1) || loy < lp.yc - lp.lyT; // // -1 fix issue 5554

                if (isZone0) { // outside selection and outside transition zone => no effect, keep original values
                    for (int x = 0; x < transformed->W; x++) {
                        transformed->L[y][x] = original->L[y][x];
                    }

                    continue;
                }

                for (int x = 0; x < transformed->W; x++) {
                    const int lox = cx + x;
                    const bool isZone0x = lox > (lp.xc + lp.lx - 1) || lox < lp.xc - lp.lxL; // -1 fix issue 5554

                    if (isZone0x) { // outside selection and outside transition zone => no effect, keep original values
                        transformed->L[y][x] = original->L[y][x];
                        continue;
                    }

                    const int begx = int (lp.xc - lp.lxL);
                    const int begy = int (lp.yc - lp.lyT);

                    int zone;
                    float localFactor = 1.f;

                    if (lp.shapmet == 0) {
                        calcTransition(lox, loy, ach, lp, zone, localFactor);
                    } else { /*if (lp.shapmet == 1)*/
                        calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                    }


                    if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                        transformed->L[y][x] = original->L[y][x];
                        continue;
                    }

                    float rs = 0.f;

                    const float csob = xlogf(1.f + rtengine::min(deltaso[loy - begy][lox - begx] / 100.f, 60.f) + 0.001f);

                    if (!recip) {
                        rs = sobelref / csob;
                    } else {
                        rs = csob / sobelref;
                    }

                    float affsob = 1.f;

                    if (lp.struexc > 0.f && rs > 0.f) {
                        const float rsob =  0.002f *  lp.struexc * rs;
                        const float minrs = 1.3f + 0.05f * lp.stru;

                        if (rs < minrs) {
                            affsob = 1.f;
                        } else {
                            affsob = 1.f / pow_F((1.f + rsob), SQR(SQR(rs - minrs)));
                        }
                    }

                    float abdelta2 = SQR(refa - origblur->a[y][x]) + SQR(refb - origblur->b[y][x]);
                    float chrodelta2 = SQR(std::sqrt(SQR(origblur->a[y][x]) + SQR(origblur->b[y][x])) - (chromaref * 327.68f));
                    float huedelta2 = abdelta2 - chrodelta2;
                    const float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - origblur->L[y][x]));
                    const float rL = origblur->L[y][x];
                    const float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, varsens);

                    if (rL > 32.768f) { //to avoid crash with very low gamut in rare cases ex : L=0.01 a=0.5 b=-0.9
                        if (zone > 0) {

                            const float difL = (rsv->L[loy - begy][lox - begx] - original->L[y][x]) * localFactor;
                            transformed->L[y][x] = CLIP(original->L[y][x] + difL * affsob * reducdE);

                            const float difa = (rsv->a[loy - begy][lox - begx] - original->a[y][x]) * localFactor;
                            transformed->a[y][x] = clipC(original->a[y][x] + difa * affsob * reducdE);

                            const float difb = (rsv->b[loy - begy][lox - begx] - original->b[y][x]) * localFactor;
                            transformed->b[y][x] = clipC(original->b[y][x] + difb * affsob * reducdE);

                        }
                    }
                }
            }
        }
    }
}





void ImProcFunctions::transit_shapedetect_retinex(int call, int senstype, LabImage * bufexporig, LabImage * bufexpfin, LabImage * bufmask, LabImage * buforigmas, float **buflight, float **bufchro, const float hueref, const float chromaref, const float lumaref, struct local_params & lp, LabImage * original, LabImage * transformed, int cx, int cy, int sk)
{

    //BENCHFUN
    {
        const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);


        float ach = lp.trans / 100.f;
        if(lp.fullim == 3 ) {//disable transit
            ach = 1.f;
        }
        
        const float varsens = lp.sensh;

        int GW = transformed->W;
        int GH = transformed->H;

        // const float refa = chromaref * cos(hueref);
        // const float refb = chromaref * sin(hueref);

        const float refa = chromaref * cos(hueref) * 327.68f;
        const float refb = chromaref * sin(hueref) * 327.68f;
        const float refL = lumaref * 327.68f;

        const bool retishow = ((lp.showmaskretimet == 1 || lp.showmaskretimet == 2));
        const bool previewreti = ((lp.showmaskretimet == 4));
        
        //balance deltaE - keep for Global
        float balanceglobal = lp.balance;
        float balanceHglobal = lp.balanceh;
        
        if(lp.fullim == 3 ) {//disable scope, but keep calculation of reducdE
            balanceglobal = 1.f;
            balanceHglobal = 1.f;
        }
        
        const float kL = balanceglobal / SQR(327.68f);
        const float kab = balancedeltaE(balanceglobal) / SQR(327.68f);
        const float kH = balanceHglobal;
        const float kch = balancedeltaE(kH);

        if (lp.colorde == 0) {
            lp.colorde = -1;//to avoid black
        }

        /*
                float ampli = 1.f + std::fabs(lp.colorde);
                ampli = 2.f + 0.5f * (ampli - 2.f);

                float darklim = 5000.f;
                float aadark = -1.f;
                float bbdark = darklim;
        */
        const bool showmas = lp.showmaskretimet == 3 ;
        // keep for Global
        float varsensglobal = varsens;
        float thrglobal = lp.thr;
        float iteratglobal = lp.iterat;
        
        if(lp.fullim == 3 ) {//Global - disable scope, but keep calculation of reducdE
            varsensglobal = 100.f;
            thrglobal = 2.f;
            iteratglobal = 2.f;
        }

        const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
        const float radius = 3.f / sk;
        const bool usemaskreti = lp.enaretiMask && senstype == 4 && !lp.enaretiMasktmap;
        float strcli = 0.03f * lp.str;

        if (lp.scalereti == 1) {
            strcli = 0.015f * lp.str;
        }

#ifdef _OPENMP
        #pragma omp parallel
#endif
        {
            gaussianBlur(original->L, origblur->L, GW, GH, radius);
            gaussianBlur(original->a, origblur->a, GW, GH, radius);
            gaussianBlur(original->b, origblur->b, GW, GH, radius);
        }

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            const float mindE = 2.f + MINSCOPE * varsensglobal * thrglobal;
            const float maxdE = 5.f + MAXSCOPE * varsensglobal * (1 + 0.1f * thrglobal);
            const float mindElim = 2.f + MINSCOPE * limscope * thrglobal;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * thrglobal);
            float previewint = 0.f; //reducdE * 10000.f * lp.colorde; //settings->previewselection;

#ifdef _OPENMP
            #pragma omp for schedule(dynamic,16)
#endif

            for (int y = ystart; y < yend; y++) {
                const int loy = cy + y;

                for (int x = xstart; x < xend; x++) {
                    const int lox = cx + x;
                    int zone;
                    float localFactor = 1.f;

                    if (lp.shapmet == 0) {
                        calcTransition(lox, loy, ach, lp, zone, localFactor);
                    } else { /*if (lp.shapmet == 1)*/
                        calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                    }


                    if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                        continue;
                    }

                    float rL = origblur->L[y][x] / 327.68f;
                    float dE;
                    float abdelta2 = 0.f;
                    float chrodelta2 = 0.f;
                    float huedelta2 = 0.f;

                    if (!usemaskreti) {
                        abdelta2 = SQR(refa - origblur->a[y][x]) + SQR(refb - origblur->b[y][x]);
                        chrodelta2 = SQR(std::sqrt(SQR(origblur->a[y][x]) + SQR(origblur->b[y][x])) - (chromaref * 327.68f));
                        huedelta2 = abdelta2 - chrodelta2;
                        dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - origblur->L[y][x]));
                    } else {
                        if (call == 2) {
                            abdelta2 = SQR(refa - buforigmas->a[y - ystart][x - xstart]) + SQR(refb - buforigmas->b[y - ystart][x - xstart]);
                            chrodelta2 = SQR(std::sqrt(SQR(buforigmas->a[y - ystart][x - xstart]) + SQR(buforigmas->b[y - ystart][x - xstart])) - (chromaref * 327.68f));
                            huedelta2 = abdelta2 - chrodelta2;
                            dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - buforigmas->L[y - ystart][x - xstart]));

                        } else {
                            abdelta2 = SQR(refa - buforigmas->a[y][x]) + SQR(refb - buforigmas->b[y][x]);
                            chrodelta2 = SQR(std::sqrt(SQR(buforigmas->a[y][x]) + SQR(buforigmas->b[y][x])) - (chromaref * 327.68f));
                            huedelta2 = abdelta2 - chrodelta2;
                            dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - buforigmas->L[y][x]));
                        }
                    }

                    float cli, clc;
                    //take into account for Global
                    float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, iteratglobal, limscope, varsensglobal) / 100.f;
                    

                    previewint = reducdE * 10000.f * lp.colorde; //settings->previewselection;

                    if (call == 2) {
                        cli = buflight[y - ystart][x - xstart];
                        clc = previewreti ? settings->previewselection * 100.f : bufchro[y - ystart][x - xstart];
                    } else {
                        cli = buflight[y][x];
                        //  clc = previewreti ? settings->previewselection * 100.f : bufchro[y][x];
                        clc = previewreti ? reducdE * 10000.f * lp.colorde : bufchro[y][x];

                    }


                    cli *= reducdE;
                    clc *= reducdE;
                    cli *= (1.f + strcli);

                    if (rL > 0.1f) { //to avoid crash with very low gamut in rare cases ex : L=0.01 a=0.5 b=-0.9
                        if (senstype == 4) {//all except color and light (TODO) and exposure
                            float lightc;

                            if (call == 2) {
                                lightc = bufexporig->L[y - ystart][x - xstart];
                            } else {
                                lightc = bufexporig->L[y][x];
                            }

                            float fli = 1.f + cli;
                            float diflc = lightc * fli - original->L[y][x];
                            diflc *= localFactor;

                            if (!showmas) {
                                transformed->L[y][x] = CLIP(original->L[y][x] + diflc);
                            } else {
                                if (call == 2) {

                                    transformed->L[y][x] =  bufmask->L[y - ystart][x - xstart];
                                } else {
                                    transformed->L[y][x] =  bufmask->L[y][x];
                                }
                            } ;

                            if (retishow && lp.fullim != 3) {
                                transformed->L[y][x] = CLIP(12000.f + diflc);
                            }
                        }

                        float fliab = 1.f;
                        float chra, chrb;

                        if (call == 2) {
                            chra = bufexporig->a[y - ystart][x - xstart];
                            chrb = bufexporig->b[y - ystart][x - xstart];
                        } else {
                            chra = bufexporig->a[y][x];
                            chrb = bufexporig->b[y][x];

                        }

                        if (senstype == 5) {
                            fliab = 1.f + clc;
                        }

                        const float difa = (chra * fliab - original->a[y][x]) * localFactor;
                        float difb = (chrb * fliab - original->b[y][x]) * localFactor;

                        transformed->a[y][x] = clipC(original->a[y][x] + difa);
                        transformed->b[y][x] = clipC(original->b[y][x] + difb);

                        if (showmas) {
                            if (call == 2) {
                                transformed->a[y][x] = bufmask->a[y - ystart][x - xstart];
                                transformed->b[y][x] = bufmask->b[y - ystart][x - xstart];
                            } else {
                                transformed->a[y][x] = bufmask->a[y][x];
                                transformed->b[y][x] = bufmask->b[y][x];

                            }
                        }

                        if (retishow && lp.fullim != 3) {
                            transformed->a[y][x] = clipC(difa);
                            transformed->b[y][x] = clipC(difb);
                        }

                        if (previewreti || lp.prevdE) {
                            difb = (bufexpfin->b[y][x] - original->b[y][x]) * localFactor;
                            transformed->a[y][x] = 0.f;
                            transformed->b[y][x] = previewint * difb;
                        }
                    }
                }
            }
        }

        if (showmas  || retishow || previewreti) {
            return;
        }

    }
}


void ImProcFunctions::transit_shapedetect(int senstype, const LabImage * bufexporig, LabImage * originalmask, float **bufchro, bool HHutili, const float hueref, const float chromaref, const float lumaref, float sobelref, float meansobel, float ** blend2, const struct local_params & lp, LabImage * original, LabImage * transformed, int cx, int cy, int sk)
{

    // BENCHFUN
    const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
    const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
    const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
    const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
    const int bfw = xend - xstart;
    const int bfh = yend - ystart;
    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disable transit
        ach = 1.f;
    }
    
    float varsens = lp.sensex;

    if (senstype == 6 || senstype == 7) { //cbdl
        varsens =  lp.senscb;
    } else if (senstype == 8) { //TM
        varsens =  lp.senstm;
    } else if (senstype == 10) { //local contrast
        varsens =  lp.senslc;
    }

    //sobel //keep in case of, not used
    sobelref /= 100.f;
    meansobel /= 100.f;

    sobelref = rtengine::min(sobelref, 60.f);

    const bool k = !(sobelref < meansobel && sobelref < lp.stru); //does not always work with noisy images

    sobelref = log1p(sobelref);

    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;
    const float previewint = settings->previewselection;

    const bool cbshow = ((lp.showmaskcbmet == 1 || lp.showmaskcbmet == 2) &&  senstype == 6);
    const bool tmshow = ((lp.showmasktmmet == 1 || lp.showmasktmmet == 2) &&  senstype == 8);
    const bool previewcb = ((lp.showmaskcbmet == 4) &&  senstype == 6 && lp.fullim != 3);
    const bool previewtm = ((lp.showmasktmmet == 4) &&  senstype == 8 && lp.fullim != 3);

    const std::unique_ptr<LabImage> origblur(new LabImage(bfw, bfh));
    std::unique_ptr<LabImage> origblurmask;

    float radius = 3.f / sk;
    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const bool usemaskcb = (lp.showmaskcbmet == 2 || lp.enacbMask || lp.showmaskcbmet == 4) && senstype == 6;
    const bool usemasktm = (lp.showmasktmmet == 2 || lp.enatmMask || lp.showmasktmmet == 4) && senstype == 8;
    const bool usemaskall = (usemaskcb  || usemasktm);

    if (usemaskall) {
        origblurmask.reset(new LabImage(bfw, bfh));

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblurmask->L, bfw, bfh, radius);
            gaussianBlur(originalmask->a, origblurmask->a, bfw, bfh, radius);
            gaussianBlur(originalmask->b, origblurmask->b, bfw, bfh, radius);
        }
    }

    if (lp.equtm  && senstype == 8) { //normalize luminance for Tone mapping , at this place we can use for others senstype!
        float *datain = new float[bfh * bfw];
        float *data = new float[bfh * bfw];

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif

        for (int y = ystart; y < yend; y++)
            for (int x = xstart; x < xend; x++) {
                datain[(y - ystart) * bfw + (x - xstart)] = original->L[y][x];
                data[(y - ystart)* bfw + (x - xstart)] = bufexporig->L[y - ystart][x - xstart];
            }

        normalize_mean_dt(data, datain, bfh * bfw, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int y = ystart; y < yend; y++)
            for (int x = xstart; x < xend; x++) {
                bufexporig->L[y - ystart][x - xstart] = data[(y - ystart) * bfw + x - xstart];
            }

        delete [] datain;
        delete [] data;
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < bfh; y++) {
            for (int x = 0; x < bfw; x++) {
                origblur->L[y][x] = original->L[y + ystart][x + xstart];
                origblur->a[y][x] = original->a[y + ystart][x + xstart];
                origblur->b[y][x] = original->b[y + ystart][x + xstart];
            }
        }

        gaussianBlur(origblur->L, origblur->L, bfw, bfh, radius);
        gaussianBlur(origblur->a, origblur->a, bfw, bfh, radius);
        gaussianBlur(origblur->b, origblur->b, bfw, bfh, radius);

    }

    const LabImage *maskptr = usemaskall ? origblurmask.get() : origblur.get();
    const float mindE = 2.f + MINSCOPE * varsens * lp.thr;
    const float maxdE = 5.f + MAXSCOPE * varsens * (1 + 0.1f * lp.thr);
    const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
    const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#if defined(__SSE2__) || defined(RT_SIMDE)
        float atan2Buffer[transformed->W] ALIGNED16;
#endif

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = ystart; y < yend; y++) {
            const int loy = cy + y;

#if defined(__SSE2__) || defined(RT_SIMDE)

            if (HHutili || senstype == 7) {
                int i = xstart;

                for (; i < xend - 3; i += 4) {
                    vfloat av = LVFU(origblur->a[y - ystart][i - xstart]);
                    vfloat bv = LVFU(origblur->b[y - ystart][i - xstart]);
                    STVFU(atan2Buffer[i], xatan2f(bv, av));
                }

                for (; i < xend; i++) {
                    atan2Buffer[i] = xatan2f(origblur->b[y - ystart][i - xstart], origblur->a[y - ystart][i - xstart]);
                }
            }

#endif

            for (int x = xstart; x < xend; x++) {
                const int lox = cx + x;

                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }


                if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

                float rhue = 0;

                if (HHutili || senstype == 7) {
#if defined(__SSE2__) || defined(RT_SIMDE)
                    rhue = atan2Buffer[x];
#else
                    rhue = xatan2f(origblur->b[y - ystart][x - xstart], origblur->a[y - ystart][x - xstart]);
#endif
                }

                const float rL = origblur->L[y - ystart][x - xstart] / 327.68f;
                float rsob = 0.f;

                if (blend2 && ((senstype == 1 && lp.struexp > 0.f) || ((senstype == 0 || senstype == 100) && lp.struco > 0.f))) {//keep in case of, not used
                    const float csob = xlogf(1.f + rtengine::min(blend2[y - ystart][x - xstart] / 100.f, 60.f) + 0.001f);

                    float rs;

                    if (k) {
                        rs = sobelref / csob;
                    } else {
                        rs = csob / sobelref;
                    }

                    if (rs > 0.f && senstype == 1) {
                        rsob =  1.1f * lp.struexp * rs;
                    } else if (rs > 0.f && (senstype == 0 || senstype == 100)) {
                        rsob =  1.1f * lp.struco * rs;
                    }
                }

                const float dE = rsob + std::sqrt(kab * (SQR(refa - maskptr->a[y - ystart][x - xstart]) + SQR(refb - maskptr->b[y - ystart][x - xstart])) + kL * SQR(refL - maskptr->L[y - ystart][x - xstart]));
                const float clc = (previewcb) ? settings->previewselection * 100.f : bufchro[y - ystart][x - xstart];
                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, varsens);
                if(lp.fullim == 3 ) {//disable scope
                    reducdE = 1.f;
                }
                float realstrchdE = reducdE * clc;

                if (rL > 0.1f) { //to avoid crash with very low gamut in rare cases ex : L=0.01 a=0.5 b=-0.9
                    if (zone > 0) {
                        float factorx = localFactor;
                        float difL = 0.f;

                        if (senstype == 6 || senstype == 8 || senstype == 10) {
                            difL = (bufexporig->L[y - ystart][x - xstart] - original->L[y][x]) * localFactor * reducdE;
                            transformed->L[y][x] = CLIP(original->L[y][x] + difL);
                        }

                        if (senstype == 7) {
                            float difab = bufexporig->L[y - ystart][x - xstart] - std::sqrt(SQR(original->a[y][x]) + SQR(original->b[y][x]));
                            float2 sincosval = xsincosf(rhue);
                            float difa = difab * sincosval.y;
                            float difb = difab * sincosval.x;
                            difa *= factorx * (100.f + realstrchdE) / 100.f;
                            difb *= factorx * (100.f + realstrchdE) / 100.f;
                            transformed->a[y][x] = clipC(original->a[y][x] + difa);
                            transformed->b[y][x] = clipC(original->b[y][x] + difb);
                        } else {
                            float flia = 1.f;
                            float flib = 1.f;
                            float chra = bufexporig->a[y - ystart][x - xstart];
                            float chrb = bufexporig->b[y - ystart][x - xstart];

                            if (senstype == 3 || senstype == 30 || senstype == 8 || senstype == 6 || senstype == 10) {
                                flia = flib = ((100.f + realstrchdE) / 100.f);
                            }


                            float difa = chra * flia - original->a[y][x];
                            float difb = chrb * flib - original->b[y][x];
                            difa *= factorx;
                            difb *= factorx;

                            transformed->a[y][x] = clipC(original->a[y][x] + difa);
                            transformed->b[y][x] = clipC(original->b[y][x] + difb);


                            if ((cbshow || tmshow)  && lp.fullim != 3) {
                                transformed->L[y][x] = CLIP(12000.f + difL);
                                transformed->a[y][x] = clipC(difa);
                                transformed->b[y][x] = clipC(difb);
                            } else if (/* previewcb  ||*/ previewtm || lp.prevdE) {
                                if (std::fabs(difb) < 500.f) {
                                    difb += difL;
                                }

                                transformed->a[y][x] = 0.f;
                                transformed->b[y][x] = previewint * difb;
                            }
                        }
                    }
                }
            }
        }
    }
}

void ImProcFunctions::InverseColorLight_Local(bool tonequ, bool tonecurv, int sp, int senstype,  struct local_params & lp, LabImage * originalmask, const LUTf& lightCurveloc, const LUTf& hltonecurveloc, const LUTf& shtonecurveloc, const LUTf& tonecurveloc, const LUTf& exlocalcurve, const LUTf& cclocalcurve, float adjustr, bool localcutili, const LUTf& lllocalcurve, bool locallutili, LabImage * original, LabImage * transformed, int cx, int cy, const float hueref, const float chromaref, const float lumaref, int sk)
{
    // BENCHFUN
    const float ach = lp.trans / 100.f;
    const float facc = (100.f + lp.chro) / 100.f; //chroma factor transition
    float varsens = lp.sens;

    if (senstype == 0) { //Color and Light
        varsens = lp.sens;
    } else if (senstype == 1) { //exposure
        varsens = lp.sensex;
    } else if (senstype == 2) { //shadows highlight
        varsens = lp.senshs;
    }

    const int GW = transformed->W;
    const int GH = transformed->H;
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;

    const std::unique_ptr<LabImage> temp(new LabImage(GW, GH));
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

    for (int y = 0; y < transformed->H; y++) {
        for (int x = 0; x < transformed->W; x++) {
            temp->L[y][x] = original->L[y][x];
            temp->a[y][x] = original->a[y][x];
            temp->b[y][x] = original->b[y][x];
        }
    }

    if (senstype == 2) { // Shadows highlight
        if (lp.shmeth == 0) {
            ImProcFunctions::shadowsHighlights(temp.get(), lp.hsena, 1, lp.highlihs, lp.shadowhs, lp.radiushs, sk, lp.hltonalhs, lp.shtonalhs);
        } else if (lp.shmeth == 1) {
            const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(GW, GH));

            lab2rgb(*temp, *tmpImage, params->icm.workingProfile);
            Glib::ustring prof = params->icm.workingProfile;

            if (tonecurv) { //Tone response curve  : does nothing if gamma=2.4 and slope=12.92 ==> gamma sRGB
                const float gamtone = params->locallab.spots.at(sp).gamSH;
                const float slotone = params->locallab.spots.at(sp).sloSH;
                int ill = 0;
                cmsHTRANSFORM dummy = nullptr;
                int locprim = 0;
                float rdx, rdy, grx, gry, blx, bly = 0.f;
                float meanx, meany, meanxe, meanye, maxdat = 0.f;
                double p[6] = {0., 0., 0., 0., 0., 0.};

                workingtrc(0, tmpImage.get(), tmpImage.get(), GW, GH, -5, prof, 2.4, 12.92310, 0, ill, 0,  0, rdx, rdy, grx, gry, blx, bly , meanx, meany, meanxe, meanye, maxdat, p, dummy, true, false, false, false);
                //  workingtrc(tmpImage.get(), tmpImage.get(), GW, GH, 5, prof, gamtone, slotone, illum, 0, dummy, false, true, true);//to keep if we want improve with illuminant and primaries
                workingtrc(0, tmpImage.get(), tmpImage.get(), GW, GH, 1, prof, gamtone, slotone, 0, ill, 0, locprim, rdx, rdy, grx, gry, blx, bly , meanx, meany, meanxe, meanye, maxdat, p, dummy, false, true, true, false);//be careful no gamut control

            }

            if (tonequ) {
                tone_eq(this, tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread);
               // tone_eq(tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread);
            }

            rgb2lab(*tmpImage, *temp, params->icm.workingProfile);
        }

    } else if (senstype == 1) { //exposure
        ImProcFunctions::exlabLocal(lp, 1.f, GH, GW, GW, GH, original, temp.get(), hltonecurveloc, shtonecurveloc, tonecurveloc, hueref, lumaref, chromaref);

        if (exlocalcurve) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < temp->H; y++) {
                for (int x = 0; x < temp->W; x++) {
                    const float lh = 0.5f * exlocalcurve[2.f * temp->L[y][x]]; // / ((lighn) / 1.9f) / 3.61f; //lh between 0 and 0 50 or more
                    temp->L[y][x] = lh;
                }
            }
        }

        if ((lp.expcomp != 0.f) || (exlocalcurve)) {
            if (lp.shadex > 0) {
                ImProcFunctions::shadowsHighlights(temp.get(), true, 1, 0, lp.shadex, 40, sk, 0, lp.shcomp);
            }
        }

        if (lp.expchroma != 0.f) {
            const float ch = (1.f + 0.02f * lp.expchroma) ;
            float chprosl;

            if (ch <= 1.f) {//convert data curve near values of slider -100 + 100, to be used after to detection shape
                chprosl = 99.f * ch - 99.f;
            } else {
                constexpr float ampli = 70.f;
                chprosl = clipChro(ampli * ch - ampli);  //ampli = 25.f arbitrary empirical coefficient between 5 and 50
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H; y++) {
                for (int x = 0; x < transformed->W; x++) {
                    const float epsi = original->L[y][x] == 0.f ? 0.001f : 0.f;
                    const float rapexp = temp->L[y][x] / (original->L[y][x] + epsi);
                    temp->a[y][x] *= (1.f + chprosl * rapexp);
                    temp->b[y][x] *= (1.f + chprosl * rapexp);
                }
            }
        }
    } else if (senstype == 0) { //Color and Light curves L C
        if (cclocalcurve && localcutili) { // C=f(C) curve
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H; y++) {
                for (int x = 0; x < transformed->W; x++) {
                    //same as in "normal"
                    const float chromat = std::sqrt(SQR(original->a[y][x]) + SQR(original->b[y][x]));
                    constexpr float ampli = 25.f;
                    const float ch = (cclocalcurve[chromat * adjustr ])  / ((chromat + 0.00001f) * adjustr); //ch between 0 and 0 50 or more
                    const float chprocu = clipChro(ampli * ch - ampli);  //ampli = 25.f arbitrary empirical coefficient between 5 and 50
                    temp->a[y][x] = original->a[y][x] * (1.f + 0.01f * chprocu);
                    temp->b[y][x] = original->b[y][x] * (1.f + 0.01f * chprocu);

                }
            }
        }

        if (lllocalcurve && locallutili) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H; y++) {
                for (int x = 0; x < transformed->W; x++) {
                    temp->L[y][x] = 0.5f * lllocalcurve[2.f * original->L[y][x]];
                }
            }
        }
    }

    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));
    std::unique_ptr<LabImage> origblurmask;
    const bool usemaskcol = (lp.enaColorMaskinv) && senstype == 0;
    const bool usemaskexp = (lp.enaExpMaskinv) && senstype == 1;
    const bool usemasksh = (lp.enaSHMaskinv) && senstype == 2;
    const bool usemaskall = (usemaskcol || usemaskexp || usemasksh);

    float radius = 3.f / sk;

    if (usemaskall) {
        origblurmask.reset(new LabImage(GW, GH));

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblurmask->L, GW, GH, radius);
            gaussianBlur(originalmask->a, origblurmask->a, GW, GH, radius);
            gaussianBlur(originalmask->b, origblurmask->b, GW, GH, radius);
        }
    }

    if (senstype == 1) {
        radius = (2.f + 0.2f * lp.blurexp) / sk;
    } else if (senstype == 0) {
        radius = (2.f + 0.2f * lp.blurcol) / sk;
    } else if (senstype == 2) {
        radius = (2.f + 0.2f * lp.blurSH) / sk;
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const LabImage *maskptr = usemaskall ? origblurmask.get() : origblur.get();
        const float mindE = 2.f + MINSCOPE * varsens * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * varsens * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < transformed->H; y++) {
            const int loy = cy + y;

            for (int x = 0; x < transformed->W; x++) {
                const float rL = origblur->L[y][x] / 327.68f;

                if (std::fabs(origblur->b[y][x]) < 0.01f) {
                    origblur->b[y][x] = 0.01f;
                }

                constexpr float th_r = 0.01f;

                if (rL > th_r) { //to avoid crash with very low gamut in rare cases ex : L=0.01 a=0.5 b=-0.9
                    const int lox = cx + x;
                    int zone;
                    float localFactor = 1.f;

                    if (lp.shapmet == 0) {
                        calcTransition(lox, loy, ach, lp, zone, localFactor);
                    } else { /*if (lp.shapmet == 1)*/
                        calcTransitionrect(lox, loy, ach, lp, zone, localFactor);//rect not good
                    }

                    //deltaE
                    float reducdE;

                    if (zone != 2) {
                        const float abdelta2 = SQR(refa - maskptr->a[y][x]) + SQR(refb - maskptr->b[y][x]);
                        const float chrodelta2 = SQR(std::sqrt(SQR(maskptr->a[y][x]) + SQR(maskptr->b[y][x])) - (chromaref * 327.68f));
                        const float huedelta2 = abdelta2 - chrodelta2;
                        const float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - maskptr->L[y][x]));
                        reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, varsens);
                    }

                    switch (zone) {
                        case 2: { // outside selection and outside transition zone => no effect, keep original values
                            transformed->L[y][x] = original->L[y][x];
                            transformed->a[y][x] = original->a[y][x];
                            transformed->b[y][x] = original->b[y][x];
                            break;
                        }

                        case 1: { // inside transition zone
                            const float factorx = 1.f - localFactor;

                            if (senstype == 0) {
                                const float epsia = original->a[y][x] == 0.f ? 0.0001f : 0.f;
                                const float epsib = original->b[y][x] == 0.f ? 0.0001f : 0.f;
                                float lumnew = original->L[y][x];
                                const float difL = (temp->L[y][x] - original->L[y][x]) * (reducdE * factorx);
                                const float difa = (temp->a[y][x] - original->a[y][x]) * (reducdE * factorx);
                                const float difb = (temp->b[y][x] - original->b[y][x]) * (reducdE * factorx);
                                const float facCa = 1.f + (difa / (original->a[y][x] + epsia));
                                const float facCb = 1.f + (difb / (original->b[y][x] + epsib));

                                if (lp.sens < 75.f) {
                                    if ((lp.ligh != 0.f || lp.cont != 0)) {
                                        lumnew = calclightinv(lumnew, lp.ligh, lightCurveloc);  //replace L-curve
                                    }

                                    const float fac = (100.f + factorx * lp.chro * reducdE) / 100.f; //chroma factor transition
                                    const float diflc = (lumnew - original->L[y][x]) * (reducdE * factorx);

                                    transformed->L[y][x] = CLIP(1.f * (original->L[y][x] + diflc + difL));
                                    transformed->a[y][x] = clipC(original->a[y][x] * fac * facCa) ;
                                    transformed->b[y][x] = clipC(original->b[y][x] * fac * facCb);
                                } else {
                                    const float fac = (100.f + factorx * lp.chro) / 100.f; //chroma factor transition

                                    if ((lp.ligh != 0.f || lp.cont != 0)) {
                                        lumnew = calclightinv(original->L[y][x], lp.ligh, lightCurveloc);
                                    }

                                    const float diflc = (lumnew - original->L[y][x]) * factorx;
                                    transformed->L[y][x] = CLIP(original->L[y][x] + diflc + difL);
                                    transformed->a[y][x] = clipC(original->a[y][x] * fac * facCa);
                                    transformed->b[y][x] = clipC(original->b[y][x] * fac * facCb);
                                }
                            } else if (senstype == 1 || senstype == 2) {
                                const float diflc = (temp->L[y][x] - original->L[y][x]) * (reducdE * factorx);
                                const float difa = (temp->a[y][x] - original->a[y][x]) * (reducdE * factorx);
                                const float difb = (temp->b[y][x] - original->b[y][x]) * (reducdE * factorx);
                                transformed->L[y][x] = CLIP(original->L[y][x] + diflc);
                                transformed->a[y][x] = clipC(original->a[y][x] + difa) ;
                                transformed->b[y][x] = clipC(original->b[y][x] + difb);
                            }

                            break;
                        }

                        case 0: { // inside selection => full effect, no transition
                            if (senstype == 0) {
                                const float epsia = original->a[y][x] == 0.f ? 0.0001f : 0.f;
                                const float epsib = original->b[y][x] == 0.f ? 0.0001f : 0.f;
                                float lumnew = original->L[y][x];
                                const float difL = (temp->L[y][x] - original->L[y][x]) * reducdE;
                                const float difa = (temp->a[y][x] - original->a[y][x]) * reducdE;
                                const float difb = (temp->b[y][x] - original->b[y][x]) * reducdE;
                                const float facCa = 1.f + difa / (original->a[y][x] + epsia);
                                const float facCb = 1.f + difb / (original->b[y][x] + epsib);

                                if (lp.sens < 75.f) {
                                    if ((lp.ligh != 0.f || lp.cont != 0)) {
                                        lumnew = calclightinv(lumnew, lp.ligh, lightCurveloc);  //replace L-curve
                                    }

                                    const float fac = (100.f + lp.chro * reducdE) / 100.f; //chroma factor transition
                                    const float diflc = (lumnew - original->L[y][x]) * reducdE;

                                    transformed->L[y][x] = CLIP(original->L[y][x] + diflc + difL);
                                    transformed->a[y][x] = clipC(original->a[y][x] * fac * facCa) ;
                                    transformed->b[y][x] = clipC(original->b[y][x] * fac * facCb);
                                } else {
                                    if ((lp.ligh != 0.f || lp.cont != 0)) {
                                        lumnew = calclightinv(original->L[y][x], lp.ligh, lightCurveloc);
                                    }

                                    transformed->L[y][x] = CLIP(lumnew + difL) ;
                                    transformed->a[y][x] = clipC(original->a[y][x] * facc * facCa);
                                    transformed->b[y][x] = clipC(original->b[y][x] * facc * facCb);
                                }
                            } else if (senstype == 1  || senstype == 2) {
                                const float diflc = (temp->L[y][x] - original->L[y][x]) * reducdE;
                                const float difa = (temp->a[y][x] - original->a[y][x]) * reducdE;
                                const float difb = (temp->b[y][x] - original->b[y][x]) * reducdE;
                                transformed->L[y][x] = CLIP(original->L[y][x] + diflc);
                                transformed->a[y][x] = clipC(original->a[y][x] + difa) ;
                                transformed->b[y][x] = clipC(original->b[y][x] + difb);
                            }
                        }
                    }
                }
            }
        }
    }
}

void ImProcFunctions::calc_ref(int sp, LabImage * original, LabImage * transformed, int cx, int cy, int oW, int oH, int sk, double & huerefblur, double & chromarefblur, double & lumarefblur, double & hueref, double & chromaref, double & lumaref, double & sobelref, float & avg, const LocwavCurve & locwavCurveden, bool locwavdenutili)
{
    if (params->locallab.enabled) {
        // always calculate hueref, chromaref, lumaref before others operations
        // use in normal mode for all modules except denoise
        struct local_params lp;
        calcLocalParams(sp, oW, oH, params->locallab, lp, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, locwavCurveden, locwavdenutili);
        int begy = lp.yc - lp.lyT;
        int begx = lp.xc - lp.lxL;
        int yEn = lp.yc + lp.ly;
        int xEn = lp.xc + lp.lx;
        float avg2 = 0.f;
        int nc2 = 0;

        for (int y = 0; y < transformed->H ; y++) //{
            for (int x = 0; x < transformed->W; x++) {
                int lox = cx + x;
                int loy = cy + y;

                if (lox >= begx && lox < xEn && loy >= begy && loy < yEn) {
                    avg2 += original->L[y][x];
                    nc2++;
                }
            }

        avg2 /= 32768.f;
        avg = avg2 / nc2;
// double precision for large summations
        double aveA = 0.;
        double aveB = 0.;
        double aveL = 0.;
        double aveChro = 0.;
        double aveAblur = 0.;
        double aveBblur = 0.;
        double aveLblur = 0.;
        double aveChroblur = 0.;

        double avesobel = 0.;
// int precision for the counters
        int nab = 0;
        int nso = 0;
        int nsb = 0;
// single precision for the result
        float avA, avB, avL;
        int spotSize = 0.88623f * rtengine::max(1.f,  lp.cir / sk);  //18
        //O.88623 = std::sqrt(PI / 4) ==> square equal to circle
        int spotSise2; // = 0.88623f * max (1,  lp.cir / sk); //18

        // very small region, don't use omp here
        LabImage *sobelL;
        LabImage *deltasobelL;
        LabImage *origsob;
        LabImage *origblur = nullptr;
        LabImage *blurorig = nullptr;

        int spotSi = 1 + 2 * rtengine::max(1.f,  lp.cir / sk);

        if (spotSi < 5) {
            spotSi = 5;
        }

        spotSise2 = (spotSi - 1) / 2;

        JaggedArray<float> blend3(spotSi, spotSi);

        origsob = new LabImage(spotSi, spotSi);
        sobelL = new LabImage(spotSi, spotSi);
        deltasobelL = new LabImage(spotSi, spotSi);
        bool isdenoise = false;

        if ((lp.noiself > 0.f || lp.noiself0 > 0.f || lp.noiself2 > 0.f || lp.wavcurvedenoi || lp.nlstr > 0 || lp.noiselc > 0.f || lp.noisecf > 0.f || lp.noisecc > 0.f) && lp.denoiena) {
            isdenoise = true;
        }

        if (isdenoise) {
            origblur = new LabImage(spotSi, spotSi);
            blurorig = new LabImage(spotSi, spotSi);

            for (int y = rtengine::max(cy, (int)(lp.yc - spotSise2)); y < rtengine::min(transformed->H + cy, (int)(lp.yc + spotSise2 + 1)); y++) {
                for (int x = rtengine::max(cx, (int)(lp.xc - spotSise2)); x < rtengine::min(transformed->W + cx, (int)(lp.xc + spotSise2 + 1)); x++) {
                    int yb = rtengine::max(cy, (int)(lp.yc - spotSise2));

                    int xb = rtengine::max(cx, (int)(lp.xc - spotSise2));

                    int z = y - yb;
                    int u = x - xb;
                    origblur->L[z][u] = original->L[y - cy][x - cx];
                    origblur->a[z][u] = original->a[y - cy][x - cx];
                    origblur->b[z][u] = original->b[y - cy][x - cx];

                }
            }

            float radius = 3.f / sk;
            {
                //No omp
                gaussianBlur(origblur->L, blurorig->L, spotSi, spotSi, radius);
                gaussianBlur(origblur->a, blurorig->a, spotSi, spotSi, radius);
                gaussianBlur(origblur->b, blurorig->b, spotSi, spotSi, radius);

            }

            for (int y = 0; y < spotSi; y++) {
                for (int x = 0; x < spotSi; x++) {
                    aveLblur += static_cast<double>(blurorig->L[y][x]);
                    aveAblur += static_cast<double>(blurorig->a[y][x]);
                    aveBblur += static_cast<double>(blurorig->b[y][x]);
                    aveChroblur += static_cast<double>(std::sqrt(SQR(blurorig->b[y - cy][x - cx]) + SQR(blurorig->a[y - cy][x - cx])));
                    nsb++;

                }
            }
        }

        //ref for luma, chroma, hue
        for (int y = rtengine::max(cy, (int)(lp.yc - spotSize)); y < rtengine::min(transformed->H + cy, (int)(lp.yc + spotSize + 1)); y++) {
            for (int x = rtengine::max(cx, (int)(lp.xc - spotSize)); x < rtengine::min(transformed->W + cx, (int)(lp.xc + spotSize + 1)); x++) {
                aveL += static_cast<double>(original->L[y - cy][x - cx]);
                aveA += static_cast<double>(original->a[y - cy][x - cx]);
                aveB += static_cast<double>(original->b[y - cy][x - cx]);
                aveChro += static_cast<double>(std::sqrt(SQR(original->b[y - cy][x - cx]) + SQR(original->a[y - cy][x - cx])));
                nab++;
            }
        }

        //ref for sobel
        for (int y = rtengine::max(cy, (int)(lp.yc - spotSise2)); y < rtengine::min(transformed->H + cy, (int)(lp.yc + spotSise2 + 1)); y++) {
            for (int x = rtengine::max(cx, (int)(lp.xc - spotSise2)); x < rtengine::min(transformed->W + cx, (int)(lp.xc + spotSise2 + 1)); x++) {
                int yb = rtengine::max(cy, (int)(lp.yc - spotSise2));

                int xb = rtengine::max(cx, (int)(lp.xc - spotSise2));

                int z = y - yb;
                int u = x - xb;
                origsob->L[z][u] = original->L[y - cy][x - cx];
                nso++;
            }
        }

        const float radius = 3.f / (sk * 1.4f); //0 to 70 ==> see skip

        SobelCannyLuma(sobelL->L, origsob->L, spotSi, spotSi, radius);
        int nbs = 0;

        for (int y = 0; y < spotSi ; y ++)
            for (int x = 0; x < spotSi ; x ++) {
                avesobel += static_cast<double>(sobelL->L[y][x]);
                nbs++;
            }

        sobelref = avesobel / nbs;

        delete sobelL;

        delete deltasobelL;
        delete origsob;
        aveL = aveL / nab;
        aveA = aveA / nab;
        aveB = aveB / nab;
        aveChro = aveChro / nab;
        aveChro /= 327.68;
        avA = aveA / 327.68;
        avB = aveB / 327.68;
        avL = aveL / 327.68;
        hueref = xatan2f(avB, avA);    //mean hue

        if (isdenoise) {
            aveLblur = aveLblur / nsb;
            aveChroblur = aveChroblur / nsb;
            aveChroblur /= 327.68;
            aveAblur = aveAblur / nsb;
            aveBblur = aveBblur / nsb;
            float avAblur = aveAblur / 327.68;
            float avBblur = aveBblur / 327.68;
            float avLblur = aveLblur / 327.68;
            huerefblur = xatan2f(avBblur, avAblur);
            chromarefblur = aveChroblur;
            lumarefblur = avLblur;
        } else {
            huerefblur = 0.f;
            chromarefblur = 0.f;
            lumarefblur = 0.f;
        }

        chromaref = aveChro;
        lumaref = avL;


        if (isdenoise) {
            delete origblur;
            delete blurorig;
        }

        lumaref = rtengine::min<float>(lumaref, 95.f); //to avoid crash
    }
}
//doc fftw3 says optimum is with size 2^a * 3^b * 5^c * 7^d * 11^e * 13^f with e+f = 0 or 1
//number for size between 18144 and 1 ==> 18000 pixels cover 99% all sensor
const int fftw_size[] = {18144, 18000, 17920, 17836, 17820, 17640, 17600, 17550, 17500, 17496, 17472, 17325, 17280, 17248, 17199, 17150, 17010, 16896, 16875, 16848, 16807,
                         16800, 16640, 16632, 16500, 16464, 16384, 16380, 16250, 16200, 16170, 16128, 16038, 16000, 15925, 15876, 15840, 15795, 15750, 15680, 15625, 15600, 15552, 15435, 15400,
                         15360, 15309, 15288, 15120, 15092, 15000, 14976, 14850, 14784, 14742, 14700, 14625, 14580, 14560, 14553, 14336, 14406, 14400, 14256, 14175, 14112, 14080, 14040, 14000, 13860,
                         13824, 13750, 13720, 13650, 13608, 13500, 13475, 13440, 13377, 13365, 13312, 13230, 13200, 13125, 13122, 13104, 13000, 12960, 12936, 12800, 12740, 12672, 12636, 12600,
                         12544, 12500, 12480, 12474, 12375, 12348, 12320, 12288, 12285, 12250, 12150, 12096, 12005, 12000, 11907, 11880, 11760, 11700, 11664, 11648, 11550, 11520, 11466, 11375,
                         11340, 11319, 11264, 11250, 11232, 11200, 11088, 11025, 11000, 10976, 10935, 10920, 10800, 10780, 10752, 10692, 10584, 10560, 10530, 10400, 10395,  10368, 10290, 10240,
                         10206, 10192, 10125, 10080, 10000, 9984, 9900, 9604, 9856, 9828, 9800, 9750, 9720, 9702, 9625, 9600, 9555, 9504, 9477, 9450, 9408, 9375, 9360, 9261, 9240,
                         9216, 9100, 9072, 9000, 8960, 8918, 8910, 8820, 8800, 8775, 8750, 8748, 8736, 8640, 8624, 8575, 8505, 8448, 8424, 8400, 8320, 8316, 8250, 8232, 8192, 8190, 8125,
                         8100, 8085, 8064, 8019, 8000, 7938, 7920, 7875, 7840, 7800, 7776, 7700, 7680, 7644, 7560, 7546, 7500, 7488, 7425, 7392, 7371, 7350, 7290, 7280, 7203, 7200, 7168,
                         7128, 7056, 7040, 7020, 7000, 6930, 6912, 6875, 6860, 6825, 6804, 6750, 6720, 6656, 6615, 6600, 6561, 6552, 6500, 6480, 6468, 6400, 6370, 6336, 6318, 6300,
                         6272, 6250, 6240, 6237, 6174, 6160, 6144, 6125, 6075, 6048, 6000, 5940, 5880, 5850, 5832, 5824, 5775, 5760, 5670, 5632, 5625, 5616, 5600, 5544, 5500, 5488,
                         5460, 5400, 5390, 5376, 5346, 5292, 5280, 5265, 5250, 5200, 5184, 5145, 5120, 5103, 5096, 5040, 5000, 4992, 4950, 4928, 4914, 4900, 4875, 4860, 4851,  4802,
                         4800, 4752, 4725, 4704, 4680, 4620, 4608, 4550, 4536, 4500, 4480, 4459, 4455, 4410, 4400, 4375, 4374, 4368, 4320, 4312, 4224, 4212, 4200, 4160, 4158, 4125,
                         4116, 4096, 4095, 4050, 4032, 4000, 3969, 3960, 3920, 3900, 3888, 3850, 3840, 3822, 3780, 3773, 3750, 3744, 3696, 3675, 3645, 3640, 3600, 3584, 3564, 3528,
                         3520, 3510, 3500, 3465, 3456, 3430, 3402, 3375, 3360, 3328, 3300, 3276, 3250, 3240, 3234, 3200, 3185, 3168, 3159, 3150, 3136, 3125, 3120, 3087, 3080, 3072,
                         3024, 3000, 2970, 2940, 2925, 2916, 2912, 2880, 2835, 2816, 2808, 2800, 2772, 2750, 2744, 2730, 2700, 2695, 2688, 2673, 2646, 2640, 2625, 2600, 2592, 2560,
                         2548, 2520, 2500, 2496, 2475, 2464, 2457, 2450, 2430, 2401, 2400, 2376, 2352, 2340, 2310, 2304, 2275, 2268, 2250, 2240, 2205, 2200, 2187, 2184, 2160, 2156,
                         2112, 2106, 2100, 2080, 2079, 2058, 2048, 2025, 2016, 2000, 1980, 1960, 1950, 1944, 1936, 1925, 1920, 1911, 1890, 1875, 1872, 1848, 1820, 1800, 1792, 1782,
                         1764, 1760, 1755, 1750, 1728, 1715, 1701, 1680, 1664, 1650, 1638, 1625, 1620, 1617, 1600, 1584, 1575, 1568, 1560, 1540, 1536, 1512, 1500, 1485, 1470, 1458,
                         1456, 1440, 1408, 1404, 1400, 1386, 1375, 1372, 1365, 1350, 1344, 1323, 1320, 1300, 1296, 1280, 1274, 1260, 1250, 1248, 1232, 1225, 1215, 1200, 1188, 1176,
                         1170, 1155, 1152, 1134, 1125, 1120, 1100, 1092, 1080, 1078, 1056, 1053, 1050, 1040, 1029, 1024, 1008, 1000, 990, 980, 975, 972, 960, 945, 936, 924, 910, 900,
                         896, 891, 882, 880, 875, 864, 840, 832, 825, 819, 810, 800, 792, 784, 780, 770, 768, 756, 750, 735, 729, 728, 720, 704, 702, 700, 693, 686, 675, 672, 660,
                         650, 648, 640, 637, 630, 625, 624, 616, 600, 594, 588, 585, 576, 567, 560, 550, 546, 540, 539, 528, 525, 520, 512, 504, 500, 495, 490, 486, 480, 468, 462, 455,
                         450, 448, 441, 440, 432, 420, 416, 405, 400, 396, 392, 390, 385, 384, 378, 375, 364, 360, 352, 351, 350, 343, 336, 330, 325, 324, 320, 315, 312, 308, 300, 297,
                         294, 288, 280, 275, 273, 270, 264, 260, 256, 252, 250, 245, 243, 240, 234, 231, 225, 224, 220, 216, 210, 208, 200, 198, 196, 195, 192, 189, 182, 180, 176, 175,
                         168, 165, 162, 160, 156, 154, 150, 147, 144, 143, 140, 135, 132, 130, 128, 126, 125, 120, 117, 112, 110, 108, 105, 104, 100, 99, 98, 96, 91, 90, 88, 84, 81,
                         80, 78, 77, 75, 72, 70, 66, 65, 64, 63, 60, 56, 55, 54, 52, 50, 49, 48, 45, 44, 42, 40, 39, 36, 35, 33, 32, 30, 28, 27, 26, 25, 24, 22, 21, 20, 18, 16, 15,
                         14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
                        };

int N_fftwsize = sizeof(fftw_size) / sizeof(fftw_size[0]);


void optfft(int N_fftwsize, int &bfh, int &bfw, int &bfhr, int &bfwr, struct local_params& lp, int H, int W, int &xstart, int &ystart, int &xend, int &yend, int cx, int cy, int fulima)
{
    int ftsizeH = 1;
    int ftsizeW = 1;
    int deltaw = 150;
    int deltah = 150;

    if (W < 4000) {
        deltaw = 80;
    }

    if (H < 4000) {
        deltah = 80;
    }


    for (int ft = 0; ft < N_fftwsize; ft++) { //find best values
        if (fftw_size[ft] <= bfh) {
            ftsizeH = fftw_size[ft];
            break;
        }
    }

    for (int ft = 0; ft < N_fftwsize; ft++) {
        if (fftw_size[ft] <= bfw) {
            ftsizeW = fftw_size[ft];
            break;
        }
    }

    if(fulima >= 2) {// if full image, the ftsizeH and ftsizeW is a bit larger (about 10 to 200 pixels) than the image dimensions so that it is fully processed (consumes a bit more resources)
        for (int ftfu = 0; ftfu < N_fftwsize; ftfu++) { //find best values
            if (fftw_size[ftfu] <= (H + deltah)) {
                ftsizeH = fftw_size[ftfu];
                break;
            }
        }

        for (int ftfu = 0; ftfu < N_fftwsize; ftfu++) { //find best values
            if (fftw_size[ftfu] <= (W + deltaw)) {
                ftsizeW = fftw_size[ftfu];
                break;
            }
        }
    }

    if (settings->verbose) {
        if (fulima == 2) {
            printf("Full image: ftsizeWF=%i ftsizeH=%i\n", ftsizeW, ftsizeH);

        } else {
            printf("ftsizeW=%i ftsizeH=%i\n", ftsizeW, ftsizeH);
        }
    }


    //optimize with size fftw
    bool reduW = false;
    bool reduH = false;
    bool exec = true;

    if (ystart == 0 && yend < H) {
        lp.ly -= (bfh - ftsizeH);
    } else if (ystart != 0 && yend == H) {
        lp.lyT -= (bfh - ftsizeH);
    } else if (ystart != 0 && yend != H) {
        if (lp.ly <= lp.lyT) {
            lp.lyT -= (bfh - ftsizeH);
        } else {
            lp.ly -= (bfh - ftsizeH);
        }
    } else if (ystart == 0 && yend == H) {
        // bfhr = ftsizeH;
        bfhr = bfh;
        reduH = true;
        exec = false;
    }

    if (xstart == 0 && xend < W) {
        lp.lx -= (bfw - ftsizeW);
    } else if (xstart != 0 && xend == W) {
        lp.lxL -= (bfw - ftsizeW);
    } else if (xstart != 0 && xend != W) {
        if (lp.lx <= lp.lxL) {
            lp.lxL -= (bfw - ftsizeW);
        } else {
            lp.lx -= (bfw - ftsizeW);
        }
    } else if (xstart == 0 && xend == W) {
        // bfwr = ftsizeW;
        bfwr = bfw;
        reduW = true;
        exec = false;
    }

    //new values optimized
    ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
    yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, H);
    xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
    xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, W);
    bfh = bfhr = yend - ystart;
    bfw = bfwr = xend - xstart;

    if (reduH && exec) {
        bfhr = ftsizeH;
    } else {
        bfhr = bfh;
    }

    if (reduW && exec) {
        bfwr = ftsizeW;
    } else {
        bfwr = bfw;
    }
}

void ImProcFunctions::BlurNoise_Local(LabImage *tmp1, LabImage * originalmask, const float hueref, const float chromaref, const float lumaref, local_params & lp, LabImage * original, LabImage * transformed, int cx, int cy, int sk)
{
//local BLUR
    //BENCHFUN

    const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
    const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
    const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
    const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);

    float ach = lp.trans / 100.f;
    if(lp.fullim == 3 ) {//disable transit
        ach = 1.f;
    }
    
    const int GW = transformed->W;
    const int GH = transformed->H;
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;
    const bool blshow = lp.showmaskblmet == 1 || lp.showmaskblmet == 2;
    const bool previewbl = lp.showmaskblmet == 4;

    //balance deltaE
    const float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);

    if (lp.colorde == 0) {
        lp.colorde = -1;//to avoid black
    }

    const float ampli = 1.5f + 0.5f * std::abs(lp.colorde);

    constexpr float darklim = 5000.f;
    constexpr float aadark = -1.f;

    const bool usemaskbl = lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 4;
    const bool usemaskall = usemaskbl;
    const float radius = 3.f / sk;
    std::unique_ptr<LabImage> origblurmask;

    const std::unique_ptr<LabImage> origblur(new LabImage(GW, GH));

    if (usemaskall) {
        origblurmask.reset(new LabImage(GW, GH));

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblurmask->L, GW, GH, radius);
            gaussianBlur(originalmask->a, origblurmask->a, GW, GH, radius);
            gaussianBlur(originalmask->b, origblurmask->b, GW, GH, radius);
        }
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        gaussianBlur(original->L, origblur->L, GW, GH, radius);
        gaussianBlur(original->a, origblur->a, GW, GH, radius);
        gaussianBlur(original->b, origblur->b, GW, GH, radius);
    }

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
        const LabImage *maskptr = usemaskall ? origblurmask.get() : origblur.get();
        const float mindE = 4.f + MINSCOPE * lp.sensbn * lp.thr;//best usage ?? with blurnoise
        const float maxdE = 5.f + MAXSCOPE * lp.sensbn * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = ystart; y < yend; y++) {
            const int loy = cy + y;

            for (int x = xstart, lox = cx + x; x < xend; x++, lox++) {
                int zone;
                float localFactor = 1.f;

                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, ach, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                }

                if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

                const float abdelta2 = SQR(refa - maskptr->a[y][x]) + SQR(refb - maskptr->b[y][x]);
                const float chrodelta2 = SQR(std::sqrt(SQR(maskptr->a[y][x]) + SQR(maskptr->b[y][x])) - chromaref * 327.68f);
                const float huedelta2 = abdelta2 - chrodelta2;
                const float dE = std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - maskptr->L[y][x]));
                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, lp.sensbn);
                if(lp.fullim == 3 ) {//disabled scope
                    reducdE = 1.f;
                }
                float difL = (tmp1->L[y - ystart][x - xstart] - original->L[y][x]) * localFactor * reducdE;
                transformed->L[y][x] = CLIP(original->L[y][x] + difL);
                const float difa = (tmp1->a[y - ystart][x - xstart] - original->a[y][x]) * localFactor * reducdE;
                const float difb = (tmp1->b[y - ystart][x - xstart] - original->b[y][x]) * localFactor * reducdE;

                transformed->a[y][x] = clipC(original->a[y][x] + difa);
                transformed->b[y][x] = clipC(original->b[y][x] + difb);

                const float maxdifab = rtengine::max(std::fabs(difa), std::fabs(difb));

                if ((blshow && lp.colorde < 0) && lp.fullim != 3)  { //show modifications with use "b"
                    //  (origshow && lp.colorde < 0) { //original Retinex
                    transformed->a[y][x] = 0.f;
                    transformed->b[y][x] = ampli * 8.f * difL * reducdE;
                    transformed->L[y][x] = CLIP(12000.f + 0.5f * ampli * difL);

                } else if ((blshow && lp.colorde > 0) && lp.fullim != 3) {//show modifications without use "b"
                    if (difL < 1000.f) {//if too low to be view use ab
                        difL += 0.5f * maxdifab;
                    }

                    transformed->L[y][x] = CLIP(12000.f + 0.5f * ampli * difL);
                    transformed->a[y][x] = clipC(ampli * difa);
                    transformed->b[y][x] = clipC(ampli * difb);
                } else if (previewbl || lp.prevdE) {//show deltaE
                    const float difbdisp = reducdE * 10000.f * lp.colorde;

                    if (transformed->L[y][x] < darklim) { //enhance dark luminance as user can see!
                        float dark = transformed->L[y][x];
                        transformed->L[y][x] = dark * aadark + darklim;
                    }

                    if (lp.colorde <= 0) {
                        transformed->a[y][x] = 0.f;
                        transformed->b[y][x] = difbdisp;
                    } else {
                        transformed->a[y][x] = -difbdisp;
                        transformed->b[y][x] = 0.f;
                    }
                }
            }
        }
    }
}

void ImProcFunctions::transit_shapedetect2(int sp, float meantm, float stdtm, int call, int senstype, const LabImage * bufexporig, const LabImage * bufexpfin, LabImage * originalmask, const float hueref, const float chromaref, const float lumaref, float sobelref, float meansobel, float ** blend2, struct local_params & lp, LabImage * original, LabImage * transformed, const LabImage *tmp1, LocalLabGradientMode grad, int cx, int cy, int sk)
{
    
    
    //initialize coordinates
    int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
    int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
    int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
    int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
    int bfw = xend - xstart;
    int bfh = yend - ystart;

// test to use in plain image
    bool execgradsh = false;
    if(lp.fullim == 3 || lp.fullim == 2 || lp.fullim == 0) {
        execgradsh = true;
    }

    const std::unique_ptr<LabImage> buftmp1(new LabImage(bfw, bfh));
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif
        for (int ir = 0; ir < bfh; ir++)
            for (int jr = 0; jr < bfw; jr++) {
                buftmp1->L[ir][jr] = 1.f;
            }
    if(grad == LocalLabGradientMode::PLAIN_IMAGE  && call != 2 && lp.strSH != 0.f && execgradsh) {//test mode GF plain image
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif
        for (int y = ystart; y < yend; y++) {
            for (int x = xstart; x < xend; x++) {
                buftmp1->L[y - ystart][x - xstart] = tmp1->L[y][x];
            }
        }    
    }
    
    //initialize scope
    float varsens = lp.sensex;//exposure

    if (senstype == 0) { //Color and light
        varsens =  lp.sens;
    } else if (senstype == 2) { //vibrance
        varsens =  lp.sensv;
    } else if (senstype == 9) { //shadowshighlight
        varsens =  lp.senshs;
    } else if (senstype == 3) { //softlight
        varsens =  lp.senssf;
    } else if (senstype == 30) { //dehaze
        varsens =  lp.sensh;
    } else if (senstype == 8) { //TM
        varsens =  lp.senstm;
    } else if (senstype == 10) { //local contrast
        varsens =  lp.senslc;
    } else if (senstype == 11) { //encoding log
        varsens = lp.sensilog;
    } else if (senstype == 20) { //common mask
        varsens = lp.sensimas;
    } else if (senstype == 31) { //ciecam
        varsens = lp.sensicie;
    } else if (senstype == 99) { //Capture sharpening
        varsens = lp.senssha;
    }
    int bfhr = bfh;
    int bfwr = bfw;

    if (lp.blurcolmask >= 0.25f && lp.fftColorMask && call == 2 && senstype == 0) {
        optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
    }

    if (lp.blurciemask >= 0.25f && lp.fftcieMask && call == 2 && senstype == 31) {
        optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
    }

    bfh = bfhr;
    bfw = bfwr;


    bool delt = lp.deltaem;
    //sobel
    sobelref /= 100.f;
    meansobel /= 100.f;

    sobelref = rtengine::min(sobelref, 60.f);

    const bool k = !(sobelref < meansobel && sobelref < lp.stru); //does not always work with noisy images

    sobelref = log1p(sobelref);

    //references Spot
    const float refa = chromaref * cos(hueref) * 327.68f;
    const float refb = chromaref * sin(hueref) * 327.68f;
    const float refL = lumaref * 327.68f;

    //to preview modifications, scope, mask
    const bool expshow = ((lp.showmaskexpmet == 1 || lp.showmaskexpmet == 2) &&  senstype == 1);
    const bool vibshow = ((lp.showmaskvibmet == 1 || lp.showmaskvibmet == 2) &&  senstype == 2);
    const bool colshow = ((lp.showmaskcolmet == 1 || lp.showmaskcolmet == 2) &&  senstype == 0);
    const bool SHshow = ((lp.showmaskSHmet == 1 || lp.showmaskSHmet == 2) &&  senstype == 9);
    const bool tmshow = ((lp.showmasktmmet == 1 || lp.showmasktmmet == 2) &&  senstype == 8);
    const bool lcshow = ((lp.showmasklcmet == 1 || lp.showmasklcmet == 2 || lp.processwa) &&  senstype == 10);
    const bool lcshow2 = ((lp.showmasklcmet == 1 || lp.processwa) &&  senstype == 10);
    const bool origshow = ((lp.showmasksoftmet == 5) &&  senstype == 3 && lp.softmet == 1);
    const bool logshow = ((lp.showmasklogmet == 1 || lp.showmasklogmet == 2) &&  senstype == 11);
    const bool cieshow = ((lp.showmaskciemet == 1 || lp.showmaskciemet == 2) &&  senstype == 31);
    const bool sharshow = ((lp.showmasksharmet == 1) &&  senstype == 99);


    const bool masshow = ((lp.showmask_met == 1) &&  senstype == 20);

    const bool previewvib = ((lp.showmaskvibmet == 4) &&  senstype == 2 && lp.fullim != 3);
    const bool previewexp = ((lp.showmaskexpmet == 5) &&  senstype == 1  && lp.fullim != 3);
    const bool previewcol = ((lp.showmaskcolmet == 5) &&  senstype == 0  && lp.fullim != 3);
    const bool previewSH = ((lp.showmaskSHmet == 4) &&  senstype == 9 && lp.fullim != 3);
    const bool previewtm = ((lp.showmasktmmet == 4) &&  senstype == 8 && lp.fullim != 3);
    const bool previewlc = ((lp.showmasklcmet == 4) &&  senstype == 10 && lp.fullim != 3);
    const bool previeworig = ((lp.showmasksoftmet == 6) &&  senstype == 3 && lp.softmet == 1);
    const bool previewmas = ((lp.showmask_met == 3) &&  senstype == 20  && lp.fullim != 3);
    const bool previewlog = ((lp.showmasklogmet == 4) &&  senstype == 11  && lp.fullim != 3);
    const bool previewcie = ((lp.showmaskciemet == 4) &&  senstype == 31  && lp.fullim != 3);
    const bool previewshar = ((lp.showmasksharmet == 2)&&  senstype == 99);

    float radius = 3.f / sk;

    if (senstype == 1) {
        radius = (2.f + 0.2f * lp.blurexp) / sk;
    } else if (senstype == 0) {
        radius = (2.f + 0.2f * lp.blurcol) / sk;
    } else if (senstype == 9) {
        radius = (2.f + 0.2f * lp.blurSH) / sk;
    }

    const std::unique_ptr<LabImage> origblur(new LabImage(bfw, bfh));
    std::unique_ptr<LabImage> origblurmask;

    //balance deltaE
    float kL = lp.balance / SQR(327.68f);
    const float kab = balancedeltaE(lp.balance) / SQR(327.68f);
    const float kH = lp.balanceh;
    const float kch = balancedeltaE(kH);

    if (lp.colorde == 0) {
        lp.colorde = -1;//to avoid black
    }

    float ampli = 1.f + std::abs(lp.colorde);
    ampli = 2.f + 0.5f * (ampli - 2.f);

    float darklim = 5000.f;
    float aadark = -1.f;
    float bbdark = darklim;
    bool usemask = true;

    if (originalmask == nullptr) {
        usemask = false;
    }

    const bool usemaskvib = (lp.showmaskvibmet == 2 || lp.enavibMask || lp.showmaskvibmet == 4) && senstype == 2;
    const bool usemaskexp = (lp.showmaskexpmet == 2 || lp.enaExpMask || lp.showmaskexpmet == 5) && senstype == 1;
    const bool usemaskcol = (lp.showmaskcolmet == 2 || lp.enaColorMask || lp.showmaskcolmet == 5) && senstype == 0;
    const bool usemaskSH = (lp.showmaskSHmet == 2 || lp.enaSHMask || lp.showmaskSHmet == 4) && senstype == 9;
    const bool usemasktm = (lp.showmasktmmet == 2 || lp.enatmMask || lp.showmasktmmet == 4) && senstype == 8;
    const bool usemasklc = (lp.showmasklcmet == 2 || lp.enalcMask || lp.showmasklcmet == 4) && senstype == 10;
    const bool usemaskmas = (lp.showmask_met == 1 || lp.ena_Mask || lp.showmask_met == 3) && senstype == 20;
    const bool usemasklog = (lp.showmasklogmet == 2 || lp.enaLMask || lp.showmasklogmet == 4) && senstype == 11;
    const bool usemaskcie = (lp.showmaskciemet == 2 || lp.enacieMask || lp.showmaskciemet == 4) && senstype == 31;
    const bool usemaskall = usemask && (usemaskexp || usemaskvib || usemaskcol || usemaskSH || usemasktm || usemasklc || usemasklog || usemaskcie || usemaskmas);

    //blur a little mask
    if (usemaskall) {
        origblurmask.reset(new LabImage(bfw, bfh));

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(originalmask->L, origblurmask->L, bfw, bfh, radius);
            gaussianBlur(originalmask->a, origblurmask->a, bfw, bfh, radius);
            gaussianBlur(originalmask->b, origblurmask->b, bfw, bfh, radius);
        }
    }



    if (lp.equtm && senstype == 8) { //normalize luminance for Tone mapping , at this place we can use for others senstype!
        float *datain = new float[bfh * bfw];
        float *data = new float[bfh * bfw];

#ifdef _OPENMP
        #pragma omp parallel for
#endif

        for (int y = ystart; y < yend; y++)
            for (int x = xstart; x < xend; x++) {
                datain[(y - ystart) * bfw + (x - xstart)] = original->L[y][x];
                data[(y - ystart)* bfw + (x - xstart)] = bufexpfin->L[y - ystart][x - xstart];
            }

        if (call == 3 || call == 2) { //improccoordinator and simpleprocess
            normalize_mean_dt(data, datain, bfw * bfh, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
        } else if (call == 1) { //dcrop
            float ma = meantm;
            float sa = stdtm;
            float ma2 = (float) params->locallab.spots.at(sp).noiselumc;
            float sa2 = (float) params->locallab.spots.at(sp).softradiustm;
            //use normalize with mean and stdv
            normalize_mean_dt(data, datain, bfw * bfh, 1.f, 1.f, ma, sa, ma2, sa2, 1.);
        }




#ifdef _OPENMP
        #pragma omp parallel for
#endif

        for (int y = ystart; y < yend; y++)
            for (int x = xstart; x < xend; x++) {
                bufexpfin->L[y - ystart][x - xstart] = data[(y - ystart) * bfw + x - xstart];
            }

        delete [] datain;
        delete [] data;
    }


    if (senstype == 8) {//strength Tone mapping
        const float repart = 1.0f - 0.01f * lp.repartm;

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

        for (int y = ystart; y < yend; y++) {
            for (int x = xstart; x < xend; x++) {
                bufexpfin->L[y - ystart][x - xstart] = intp(repart, original->L[y][x], bufexpfin->L[y - ystart][x - xstart]);
                bufexpfin->a[y - ystart][x - xstart] = intp(repart, original->a[y][x], bufexpfin->a[y - ystart][x - xstart]);
                bufexpfin->b[y - ystart][x - xstart] = intp(repart, original->b[y][x], bufexpfin->b[y - ystart][x - xstart]);
            }
        }
    }


#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < bfh; y++) {
            for (int x = 0; x < bfw; x++) {
                origblur->L[y][x] = original->L[y + ystart][x + xstart];
                origblur->a[y][x] = original->a[y + ystart][x + xstart];
                origblur->b[y][x] = original->b[y + ystart][x + xstart];
            }
        }

        gaussianBlur(origblur->L, origblur->L, bfw, bfh, radius);
        gaussianBlur(origblur->a, origblur->a, bfw, bfh, radius);
        gaussianBlur(origblur->b, origblur->b, bfw, bfh, radius);

    }


    //choice between original and mask
    const LabImage *maskptr = usemaskall ? origblurmask.get() : origblur.get();

    //parameters deltaE
    int limvarsens = 50;//begin change calculation reduction deltaE
    if ((senstype == 11 || (senstype == 31 && lp.islogcie)) && (varsens < limvarsens)) {
        //increase a bit lp.thr and lp.iterat and kL if HDR only with log encoding and CAM16 Jz
        lp.thr *= 1.2f;
        lp.iterat *= 1.2f;
        kL /= 1.2f;
    } else if ((senstype == 11 || (senstype == 31  && lp.islogcie)) && (varsens >= limvarsens)) {//for log encoding and cam16 is log encode used
        lp.thr += 10.f;//increase  threshold deltaE
        lp.thr = LIM(lp.thr, 0.f, 15.0f);
        lp.balance -= 2.3f;
        lp.balance = LIM(lp.balance, 0.05f, 2.5f); // down balance ab-L     
        kL = lp.balance / SQR(327.68f);
    }
    
    const float mindE = 2.f + MINSCOPE * varsens * lp.thr;
    const float maxdE = 5.f + MAXSCOPE * varsens * (1 + 0.1f * lp.thr);
    const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
    const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#if defined(__SSE2__) || defined(RT_SIMDE)
//        float atan2Buffer[transformed->W] ALIGNED16;//keep in case of
#endif

#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < bfh; y++) {

            const int loy = y + ystart + cy;
#if defined(__SSE2__) || defined(RT_SIMDE)
            /* //keep in case of
                        int i = 0;

                        for (; i < bfw - 3; i += 4) {
                            vfloat av = LVFU(maskptr->a[y][i]);
                            vfloat bv = LVFU(maskptr->b[y][i]);
                            STVFU(atan2Buffer[i], xatan2f(bv, av));
                        }

                        for (; i < bfw; i++) {
                            atan2Buffer[i] = xatan2f(maskptr->b[y][i], maskptr->a[y][i]);
                        }
            */
#endif

            for (int x = 0; x < bfw; x++) {
                const int lox = x + xstart + cx;
                int zone;
                float localFactor = 1.f;
                float achm = lp.trans / 100.f;
                if(lp.fullim == 3 ) {//disable transit
                    achm = 1.f;
                }

                //calculate transition
                if (lp.shapmet == 0) {
                    calcTransition(lox, loy, achm, lp, zone, localFactor);
                } else { /*if (lp.shapmet == 1)*/
                    calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
                }
                if(lp.fullim == 3 ) {//disable scope
                    localFactor = 1.f;
                }

//                float hueh = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)
//                hueh = atan2Buffer[x];
#else
//                hueh = xatan2f(maskptr->b[y][x], maskptr->a[y][x]);
#endif

                float rsob = 0.f;

                //calculate additive sobel to deltaE
                if (blend2 && ((senstype == 1 && lp.struexp > 0.f) || ((senstype == 0) && lp.struco > 0.f))) {
                    const float csob = xlogf(1.f + rtengine::min(blend2[y][x] / 100.f, 60.f) + 0.001f);

                    float rs;

                    if (k) {
                        rs = sobelref / csob;
                    } else {
                        rs = csob / sobelref;
                    }

                    if (rs > 0.f && senstype == 1) {
                        rsob =  1.1f * lp.struexp * rs;
                    } else if (rs > 0.f && (senstype == 0)) {
                        rsob =  1.1f * lp.struco * rs;
                    }
                }


                //deltaE
                float abdelta2 = SQR(refa - maskptr->a[y][x]) + SQR(refb - maskptr->b[y][x]);
                float chrodelta2 = SQR(std::sqrt(SQR(maskptr->a[y][x]) + SQR(maskptr->b[y][x])) - (chromaref * 327.68f));
                float huedelta2 = abdelta2 - chrodelta2;

                const float dE = rsob + std::sqrt(kab * (kch * chrodelta2 + kH * huedelta2) + kL * SQR(refL - maskptr->L[y][x]));
                //reduction action with deltaE
                float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, varsens);
                if(lp.fullim == 3 ) {//disable scope
                    reducdE = 1.f;
                }
                if ((senstype == 11 || ( senstype == 31 && lp.islogcie)) && (varsens >= limvarsens)) {
                    int maxvarsens = 90;//arbitrary value to get maximum incidence  
                    float ared = (1.f - reducdE) / (maxvarsens - limvarsens);
                    float bred = 1.f - ared * maxvarsens;
                    reducdE = ared * varsens + bred;
                    reducdE = LIM(reducdE, 0.1f, 1.f);
                }
                if(varsens == 100.f) {
                    reducdE = 1.f;
                }
                float factgrad = 1.f;
                // test to use in plain image
                if(grad == LocalLabGradientMode::PLAIN_IMAGE  && call == 1 && lp.strSH != 0.f  && execgradsh) {
                    factgrad = buftmp1->L[y][x];

                } 
                
                float cli = (factgrad * bufexpfin->L[y][x] - bufexporig->L[y][x] );
                float cla = (bufexpfin->a[y][x] - bufexporig->a[y][x]);
                float clb = (bufexpfin->b[y][x] - bufexporig->b[y][x]);

                if (delt) {//mask deltaE
                    if(grad == LocalLabGradientMode::PLAIN_IMAGE  && call == 1 && lp.strSH != 0.f && execgradsh) { //test mode plain image   
                        cli = (buftmp1->L[y + ystart][x + xstart] * bufexpfin->L[y][x] - original->L[y + ystart][x + xstart]);                        
                    } else {
                        cli = (bufexpfin->L[y][x] - original->L[y + ystart][x + xstart]);                        
                    }
                    cla = bufexpfin->a[y][x] - original->a[y + ystart][x + xstart];
                    clb = bufexpfin->b[y][x] - original->b[y + ystart][x + xstart];
                }

                if (lp.blwh) {
                    cla = 0.f;
                    clb = 0.f;
                }


                const float realstrdE = reducdE * cli;
                const float realstradE = reducdE * cla;
                const float realstrbdE = reducdE * clb;

                float factorx = localFactor;
                if (zone > 0) {
                    //simplified transformed with deltaE and transition
                    transformed->L[y + ystart][x + xstart] = clipLoc(original->L[y + ystart][x + xstart]  + factorx * realstrdE );//clipLoc now do nothing...just keep in ace off
                    float diflc = factorx * realstrdE;
                    transformed->a[y + ystart][x + xstart] = clipC(original->a[y + ystart][x + xstart] + factorx * realstradE);
                    const float difa = factorx * realstradE;
                    transformed->b[y + ystart][x + xstart] = clipC(original->b[y + ystart][x + xstart] + factorx * realstrbdE);
                    const float difb = factorx * realstrbdE;
                    float maxdifab = rtengine::max(std::fabs(difa), std::fabs(difb));

                    if (((expshow || vibshow || colshow || SHshow || tmshow || lcshow || logshow || cieshow || sharshow || origshow || masshow) && lp.colorde < 0 && (lp.fullim != 3))  || lcshow2 ) { //show modifications with use "b"
                        //  (origshow && lp.colorde < 0) { //original Retinex
                        transformed->a[y + ystart][x + xstart] = 0.f;
                        transformed->b[y + ystart][x + xstart] = ampli * 8.f * diflc * reducdE;
                        transformed->L[y + ystart][x + xstart] = CLIP(12000.f + 0.5f * ampli * diflc);

                    } else if (((expshow || vibshow || colshow || SHshow || tmshow || lcshow || logshow || cieshow || sharshow || origshow || masshow) && lp.colorde > 0 && (lp.fullim != 3)) || lcshow2) {//show modifications without use "b"
                        if (diflc < 1000.f) {//if too low to be view use ab
                            diflc += 0.5f * maxdifab;
                        }

                        transformed->L[y + ystart][x + xstart] = CLIP(12000.f + 0.5f * ampli * diflc);
                        transformed->a[y + ystart][x + xstart] = clipC(ampli * difa);
                        transformed->b[y + ystart][x + xstart] = clipC(ampli * difb);
                    } else if (previewexp || previewvib || previewcol || previewSH || previewtm || previewlc || previewlog || previewcie || previewshar || previeworig || previewmas || lp.prevdE) {//show deltaE
                        float difbdisp = reducdE * 10000.f * lp.colorde;

                        if (transformed->L[y + ystart][x + xstart] < darklim) { //enhance dark luminance as user can see!
                            float dark = transformed->L[y + ystart][x + xstart];
                            transformed->L[y + ystart][x + xstart] = dark * aadark + bbdark;
                        }

                        if (lp.colorde <= 0) {
                            transformed->a[y + ystart][x + xstart] = 0.f;
                            transformed->b[y + ystart][x + xstart] = difbdisp;
                        } else {
                            transformed->a[y + ystart][x + xstart] = -difbdisp;
                            transformed->b[y + ystart][x + xstart] = 0.f;
                        }
                    }
                }
            }
        }
    }
}




void ImProcFunctions::exposure_pde(float * dataor, float * datain, float * dataout, int bfw, int bfh, float thresh, float mod)
/* Jacques Desmis July 2019
** adapted from Ipol Copyright 2009-2011 IPOL Image Processing On Line http://www.ipol.im/
*/
{

    //BENCHFUN
#ifdef RT_FFTW3F_OMP
    if (multiThread) {
        fftwf_init_threads();
        fftwf_plan_with_nthreads(omp_get_max_threads());
    }

#endif
    float *data_fft, *data_tmp, *data;

    if (NULL == (data_tmp = (float *) fftwf_malloc(sizeof(float) * bfw * bfh))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    ImProcFunctions::discrete_laplacian_threshold(data_tmp, datain, bfw, bfh, thresh);

    if (NULL == (data_fft = (float *) fftwf_malloc(sizeof(float) * bfw * bfh))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    if (NULL == (data = (float *) fftwf_malloc(sizeof(float) * bfw * bfh))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    const auto dct_fw = fftwf_plan_r2r_2d(bfh, bfw, data_tmp, data_fft, FFTW_REDFT10, FFTW_REDFT10, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
    fftwf_execute(dct_fw);

    fftwf_free(data_tmp);

    /* solve the Poisson PDE in Fourier space */
    /* 1. / (float) (bfw * bfh)) is the DCT normalisation term, see libfftw */
    ImProcFunctions::rex_poisson_dct(data_fft, bfw, bfh, 1. / (double)(bfw * bfh));

    const auto dct_bw = fftwf_plan_r2r_2d(bfh, bfw, data_fft, data, FFTW_REDFT01, FFTW_REDFT01, FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
    fftwf_execute(dct_bw);
    fftwf_destroy_plan(dct_fw);
    fftwf_destroy_plan(dct_bw);
    fftwf_free(data_fft);
    fftwf_cleanup();

#ifdef RT_FFTW3F_OMP

    if (multiThread) {
        fftwf_cleanup_threads();
    }

#endif

    normalize_mean_dt(data, dataor, bfw * bfh, mod, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
    {

#ifdef _OPENMP
        #pragma omp parallel for
#endif

        for (int y = 0; y < bfh ; y++) {
            for (int x = 0; x < bfw; x++) {
                dataout[y * bfw + x] = clipLoc(data[y * bfw + x]);
            }
        }
    }

    fftwf_free(data);
}

void ImProcFunctions::fftw_convol_blur(float * input, float * output, int bfw, int bfh, float radius, int fftkern, int algo)
{
    /*
        ** Jacques Desmis june 2019 - inspired by Copyright 2013 IPOL Image Processing On Line http://www.ipol.im/
        ** when I read documentation on various FFT blur we found 2 possibilities
        ** 0) kernel gauss is used with "normal" data
        ** 1) kernel gauss is used with FFT
        ** fftkern allows to change 0) or 1) and test  It seems the good solution is with 0, but I keep the code in case of ??

        ** input real data to blur
        ** output real data blurred with radius
        ** bfw bfh width and high area
        ** radius = sigma for kernel
        ** n_x n_y relative width and high for kernel
        ** Gaussian blur is given by G(x,y) = (1/2*PI*sigma) * exp(-(x2 + y2) / 2* sigma2)
        ** its traduction in Fourier transform is G(x,y) =  exp((-sigma)*(PI * x2 + PI * y2)), for some authors it is not sigma but sigma^2..I have tried...huge differences with Gaussianblur
        ** after several test the only result that works very well is with fftkern = 0 and algo = 0, and as there is differences with Gaussianblur, I put an empirical correction in Ipretinex and Iplocalcontrast
        ** you can enabled or disabled this function with rtsettings.fftwsigma in options. By default empirical formula is disabled
        ** in fact no importance....if it is this function (for sigma) or another... we are not in research :)
    */
    //BENCHFUN

#ifdef RT_FFTW3F_OMP
    if (multiThread) {
        fftwf_init_threads();
        fftwf_plan_with_nthreads(omp_get_max_threads());
    }

#endif


    float *out; //for FFT data
    float *kern = nullptr;//for kernel gauss
    float *outkern = nullptr;//for FFT kernel
    fftwf_plan p;
    fftwf_plan pkern;//plan for FFT
    int image_size, image_sizechange;
    float n_x = 1.f;
    float n_y = 1.f;//relative coordinates for kernel Gauss
    float radsig = 1.f;

    out = (float*) fftwf_malloc(sizeof(float) * (bfw * bfh));//allocate real data for FFT

    if (fftkern == 1) { //allocate memory FFT if kernel fft = 1
        // kern = new float[bfw * bfh];
        kern = (float*) fftwf_malloc(sizeof(float) * (bfw * bfh));//allocate real data for FFT
        outkern = (float*) fftwf_malloc(sizeof(float) * (bfw * bfh));//allocate real data for FFT
    }

    /*compute the Fourier transform of the input data*/

    p = fftwf_plan_r2r_2d(bfh, bfw, input, out, FFTW_REDFT10, FFTW_REDFT10,  FFTW_ESTIMATE);//FFT 2 dimensions forward  FFTW_MEASURE FFTW_ESTIMATE

    fftwf_execute(p);
    fftwf_destroy_plan(p);

    /*define the gaussian constants for the convolution kernel*/
    if (algo == 0) {
        n_x = rtengine::RT_PI / (double) bfw / std::sqrt(2.); //ipol
        n_y = rtengine::RT_PI / (double) bfh / std::sqrt(2.);
    } else if (algo == 1) {
        n_x = 1.f / bfw; //gauss
        n_y = 1.f / bfh;
        radsig = 1.f / (2.f * rtengine::RT_PI_F * radius * radius);//gauss
    }

    n_x = n_x * n_x;
    n_y = n_y * n_y;

    image_size = bfw * bfh;
    image_sizechange = 4 * image_size;

    if (fftkern == 1) { //convolution with FFT kernel
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int j = 0; j < bfh; j++) {
            int index = j * bfw;

            for (int i = 0; i < bfw; i++)
                if (algo == 0) {
                    kern[ i + index] = exp((float)(-radius * radius) * (n_x * i * i + n_y * j * j)); //calculate Gauss kernel Ipol formula
                } else if (algo == 1) {
                    kern[ i + index] = radsig * exp((float)(-(n_x * i * i + n_y * j * j) / (2.f * radius * radius))); //calculate Gauss kernel  with Gauss formula
                }
        }

        /*compute the Fourier transform of the kernel data*/
        pkern = fftwf_plan_r2r_2d(bfh, bfw, kern, outkern, FFTW_REDFT10, FFTW_REDFT10, FFTW_ESTIMATE); //FFT 2 dimensions forward
        fftwf_execute(pkern);
        fftwf_destroy_plan(pkern);

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int j = 0; j < bfh; j++) {
            int index = j * bfw;

            for (int i = 0; i < bfw; i++) {
                out[i + index] *= outkern[i + index];    //apply Gauss kernel with FFT
            }
        }

        fftwf_free(outkern);
        fftwf_free(kern);

        //   delete [] kern;

    } else if (fftkern == 0) {//without FFT kernel
        if (algo == 0) {
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int j = 0; j < bfh; j++) {
                int index = j * bfw;

                for (int i = 0; i < bfw; i++) {
                    out[i + index] *= exp((float)(-radius * radius) * (n_x * i * i + n_y * j * j));    //apply Gauss kernel without FFT - some authors says radius*radius but differences with Gaussianblur
                }
            }
        } else if (algo == 1) {
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int j = 0; j < bfh; j++) {
                int index = j * bfw;

                for (int i = 0; i < bfw; i++) {
                    out[i + index] *= radsig * exp((float)(-(n_x * i * i + n_y * j * j) / (2.f * radius * radius)));    //calculate Gauss kernel  with Gauss formula
                }
            }
        }
    }

    p = fftwf_plan_r2r_2d(bfh, bfw, out, output, FFTW_REDFT01, FFTW_REDFT01, FFTW_ESTIMATE);//FFT 2 dimensions backward
    fftwf_execute(p);

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int index = 0; index < image_size; index++) { //restore data
        output[index] /= image_sizechange;
    }

    fftwf_destroy_plan(p);
    fftwf_free(out);

#ifdef RT_FFTW3F_OMP

    if (multiThread) {
        fftwf_cleanup_threads();
    }

#endif
}

void ImProcFunctions::fftw_convol_blur2(float **input2, float **output2, int bfw, int bfh, float radius, int fftkern, int algo)
{
    MyMutex::MyLock lock(*fftwMutex);

    float *input = nullptr;

    if (NULL == (input = (float *) fftwf_malloc(sizeof(float) * bfw * bfh))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

    float *output = nullptr;

    if (NULL == (output = (float *) fftwf_malloc(sizeof(float) * bfw * bfh))) {
        fprintf(stderr, "allocation error\n");
        abort();
    }

#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            input[y * bfw + x] =  input2[y][x];
        }
    }

    ImProcFunctions::fftw_convol_blur(input, output, bfw, bfh, radius, fftkern, algo);

#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            output2[y][x] = output[y * bfw + x];
        }
    }

    fftwf_free(input);
    fftwf_free(output);
}


void ImProcFunctions::fftw_tile_blur(int GW, int GH, int tilssize, int max_numblox_W, int min_numblox_W, float **tmp1, int numThreads, double radius)
{
    //BENCHFUN
    float epsil = 0.001f / (tilssize * tilssize);
    fftwf_plan plan_forward_blox[2];
    fftwf_plan plan_backward_blox[2];

    array2D<float> tilemask_in(tilssize, tilssize);
    array2D<float> tilemask_out(tilssize, tilssize);

    float *Lbloxtmp  = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * tilssize * tilssize * sizeof(float)));
    float *fLbloxtmp = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * tilssize * tilssize * sizeof(float)));

    int nfwd[2] = {tilssize, tilssize};

    //for DCT:
    fftw_r2r_kind fwdkind[2] = {FFTW_REDFT10, FFTW_REDFT10};
    fftw_r2r_kind bwdkind[2] = {FFTW_REDFT01, FFTW_REDFT01};

    // Creating the plans with FFTW_MEASURE instead of FFTW_ESTIMATE speeds up the execute a bit
    plan_forward_blox[0]  = fftwf_plan_many_r2r(2, nfwd, max_numblox_W, Lbloxtmp, nullptr, 1, tilssize * tilssize, fLbloxtmp, nullptr, 1, tilssize * tilssize, fwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_backward_blox[0] = fftwf_plan_many_r2r(2, nfwd, max_numblox_W, fLbloxtmp, nullptr, 1, tilssize * tilssize, Lbloxtmp, nullptr, 1, tilssize * tilssize, bwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_forward_blox[1]  = fftwf_plan_many_r2r(2, nfwd, min_numblox_W, Lbloxtmp, nullptr, 1, tilssize * tilssize, fLbloxtmp, nullptr, 1, tilssize * tilssize, fwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_backward_blox[1] = fftwf_plan_many_r2r(2, nfwd, min_numblox_W, fLbloxtmp, nullptr, 1, tilssize * tilssize, Lbloxtmp, nullptr, 1, tilssize * tilssize, bwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    fftwf_free(Lbloxtmp);
    fftwf_free(fLbloxtmp);
    const int border = rtengine::max(2, tilssize / 16);

    for (int i = 0; i < tilssize; ++i) {
        float i1 = abs((i > tilssize / 2 ? i - tilssize + 1 : i));
        float vmask = (i1 < border ? SQR(sin((rtengine::RT_PI_F * i1) / (2 * border))) : 1.0f);
        float vmask2 = (i1 < 2 * border ? SQR(sin((rtengine::RT_PI_F * i1) / (2 * border))) : 1.0f);

        for (int j = 0; j < tilssize; ++j) {
            float j1 = abs((j > tilssize / 2 ? j - tilssize + 1 : j));
            tilemask_in[i][j] = (vmask * (j1 < border ? SQR(sin((rtengine::RT_PI_F * j1) / (2 * border))) : 1.0f)) + epsil;
            tilemask_out[i][j] = (vmask2 * (j1 < 2 * border ? SQR(sin((rtengine::RT_PI_F * j1) / (2 * border))) : 1.0f)) + epsil;

        }
    }

    float *LbloxArray[numThreads];
    float *fLbloxArray[numThreads];

    const int numblox_W = ceil((static_cast<float>(GW)) / offset) + 2;
    const int numblox_H = ceil((static_cast<float>(GH)) / offset) + 2;

    array2D<float> Lresult(GW, GH, ARRAY2D_CLEAR_DATA);
    array2D<float> totwt(GW, GH, ARRAY2D_CLEAR_DATA); //weight for combining DCT blocks

    for (int i = 0; i < numThreads; ++i) {
        LbloxArray[i]  = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * tilssize * tilssize * sizeof(float)));
        fLbloxArray[i] = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * tilssize * tilssize * sizeof(float)));
    }

#ifdef _OPENMP
    int masterThread = omp_get_thread_num();
#endif
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#ifdef _OPENMP
        int subThread = masterThread * 1 + omp_get_thread_num();
#else
        int subThread = 0;
#endif
        float *Lblox = LbloxArray[subThread];
        float *fLblox = fLbloxArray[subThread];
        float pBuf[GW + tilssize + 2 * offset] ALIGNED16;
#ifdef _OPENMP
        #pragma omp for
#endif

        for (int vblk = 0; vblk < numblox_H; ++vblk) {

            int top = (vblk - 1) * offset;
            float * datarow = pBuf + offset;

            for (int i = 0; i < tilssize; ++i) {
                int row = top + i;
                int rr = row;

                if (row < 0) {
                    rr = rtengine::min(-row, GH - 1);
                } else if (row >= GH) {
                    rr = rtengine::max(0, 2 * GH - 2 - row);
                }

                for (int j = 0; j < GW; ++j) {
                    datarow[j] = (tmp1[rr][j]);
                }

                for (int j = -1 * offset; j < 0; ++j) {
                    datarow[j] = datarow[rtengine::min(-j, GW - 1)];
                }

                for (int j = GW; j < GW + tilssize + offset; ++j) {
                    datarow[j] = datarow[rtengine::max(0, 2 * GW - 2 - j)];
                }//now we have a padded data row

                for (int hblk = 0; hblk < numblox_W; ++hblk) {
                    int left = (hblk - 1) * offset;
                    int indx = (hblk) * tilssize; //index of block in malloc

                    if (top + i >= 0 && top + i < GH) {
                        int j;

                        for (j = 0; j < rtengine::min((-left), tilssize); ++j) {
                            Lblox[(indx + i)*tilssize + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }

                        for (; j < rtengine::min(tilssize, GW - left); ++j) {
                            Lblox[(indx + i)*tilssize + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                            totwt[top + i][left + j] += tilemask_in[i][j] * tilemask_out[i][j];
                        }

                        for (; j < tilssize; ++j) {
                            Lblox[(indx + i)*tilssize + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }
                    } else {
                        for (int j = 0; j < tilssize; ++j) {
                            Lblox[(indx + i)*tilssize + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }
                    }

                }

            }//end of filling block row

            //fftwf_print_plan (plan_forward_blox);
            if (numblox_W == max_numblox_W) {
                fftwf_execute_r2r(plan_forward_blox[0], Lblox, fLblox);    // DCT an entire row of tiles
            } else {
                fftwf_execute_r2r(plan_forward_blox[1], Lblox, fLblox);    // DCT an entire row of tiles
            }

            const float n_xy = rtengine::SQR(rtengine::RT_PI / tilssize);

            //radius = 30.f;
            for (int hblk = 0; hblk < numblox_W; ++hblk) {
                int blkstart = hblk * tilssize * tilssize;

                for (int j = 0; j < tilssize; j++) {
                    int index = j * tilssize;

                    for (int i = 0; i < tilssize; i++) {
                        fLblox[blkstart + index + i] *= exp((float)(-radius) * (n_xy * rtengine::SQR(i) + n_xy * rtengine::SQR(j)));
                    }
                }
            }//end of horizontal block loop

            //now perform inverse FT of an entire row of blocks
            if (numblox_W == max_numblox_W) {
                fftwf_execute_r2r(plan_backward_blox[0], fLblox, Lblox);    //for DCT
            } else {
                fftwf_execute_r2r(plan_backward_blox[1], fLblox, Lblox);    //for DCT
            }

            int topproc = (vblk - 1) * offset;
            const int lnumblox_W = ceil((static_cast<float>(GW)) / offset);
            const float DCTnorm = 1.0f / (4 * tilssize * tilssize); //for DCT

            int imin = rtengine::max(0, - topproc);
            int bottom = rtengine::min(topproc + tilssize, GH);
            int imax = bottom - topproc;

            for (int i = imin; i < imax; ++i) {
                for (int hblk = 0; hblk < lnumblox_W; ++hblk) {
                    int left = (hblk - 1) * offset;
                    int right  = rtengine::min(left + tilssize, GW);
                    int jmin = rtengine::max(0, -left);
                    int jmax = right - left;
                    int indx = hblk * tilssize;

                    for (int j = jmin; j < jmax; ++j) {
                        Lresult[topproc + i][left + j] += tilemask_out[i][j] * Lblox[(indx + i) * tilssize + j] * DCTnorm; //for DCT
                    }
                }
            }
        }//end of vertical block loop
    }

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int i = 0; i < GH; ++i) {
        for (int j = 0; j < GW; ++j) {
            tmp1[i][j] = Lresult[i][j] / totwt[i][j];
            tmp1[i][j] = clipLoc(tmp1[i][j]);
        }
    }

    for (int i = 0; i < numThreads; ++i) {
        fftwf_free(LbloxArray[i]);
        fftwf_free(fLbloxArray[i]);
    }

    fftwf_destroy_plan(plan_forward_blox[0]);
    fftwf_destroy_plan(plan_backward_blox[0]);
    fftwf_destroy_plan(plan_forward_blox[1]);
    fftwf_destroy_plan(plan_backward_blox[1]);
    fftwf_cleanup();
}

void ImProcFunctions::wavcbd(wavelet_decomposition &wdspot, int level_bl, int maxlvl,
                             const LocwavCurve& locconwavCurve, bool locconwavutili, float sigm, float offs, float chromalev, int sk)
{
    if (locconwavCurve && locconwavutili) {
        float mean[10];
        float meanN[10];
        float sigma[10];
        float sigmaN[10];
        float MaxP[10];
        float MaxN[10];

#ifdef _OPENMP
        const int numThreads = omp_get_max_threads();
#else
        const int numThreads = 1;
#endif
        Evaluate2(wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic) collapse(2) if (multiThread)
#endif

        for (int dir = 1; dir < 4; dir++) {
            for (int level = level_bl; level < maxlvl; ++level) {
                const int W_L = wdspot.level_W(level);
                const int H_L = wdspot.level_H(level);
                float mea[9];

                float* const* wav_L = wdspot.level_coeffs(level);
                //offset
                float rap = offs * mean[level] - 2.f * sigm * sigma[level];

                if (rap > 0.f) {
                    mea[0] = rap;
                } else {
                    mea[0] = mean[level] / 6.f;
                }

                rap =  offs * mean[level] - sigm * sigma[level];

                if (rap > 0.f) {
                    mea[1] = rap;
                } else {
                    mea[1] = mean[level] / 2.f;
                }

                mea[2] = offs * mean[level]; // 50% data
                mea[3] = offs * mean[level] + sigm * sigma[level] / 2.f;
                mea[4] = offs * mean[level] + sigm * sigma[level]; //66%
                mea[5] = offs * mean[level] + sigm * 1.2f * sigma[level];
                mea[6] = offs * mean[level] + sigm * 1.5f * sigma[level]; //
                mea[7] = offs * mean[level] + sigm * 2.f * sigma[level]; //95%
                mea[8] = offs * mean[level] + sigm * 2.5f * sigma[level]; //99%

                float cpMul = 200.f * (locconwavCurve[level * 55.5f] - 0.5f);

                if (cpMul > 0.f) {
                    cpMul *= 3.5f;
                }

                cpMul /= sk;

                for (int i = 0; i < W_L * H_L; i++) {
                    const float WavCL = std::fabs(wav_L[dir][i]);
                    float beta;

                    //reduction amplification: max action between mean / 2 and mean + sigma
                    // arbitrary coefficient, we can add a slider !!
                    if (WavCL < mea[0]) {
                        beta = 0.6f;    //preserve very low contrast (sky...)
                    } else if (WavCL < mea[1]) {
                        beta = 0.8f;
                    } else if (WavCL < mea[2]) {
                        beta = 1.f;    //standard
                    } else if (WavCL < mea[3]) {
                        beta = 1.f;
                    } else if (WavCL < mea[4]) {
                        beta = 0.8f;    //+sigma
                    } else if (WavCL < mea[5]) {
                        beta = 0.6f;
                    } else if (WavCL < mea[6]) {
                        beta = 0.4f;
                    } else if (WavCL < mea[7]) {
                        beta = 0.2f;    // + 2 sigma
                    } else if (WavCL < mea[8]) {
                        beta = 0.1f;
                    } else {
                        beta = 0.0f;
                    }

                    const float alpha = rtengine::max((1024.f + 15.f * cpMul * beta) / 1024.f, 0.02f) ;
                    wav_L[dir][i] *= alpha * chromalev;
                }
            }
        }
    }
}

void ImProcFunctions::Compresslevels(float **Source, int W_L, int H_L, float compression, float detailattenuator, float thres, float mean, float maxp, float meanN, float maxN, float madL)
{
    //J.Desmis 12-2019

    float exponent;

    if (detailattenuator > 0.f && detailattenuator < 0.05f) {
        const float betemp = expf(-(2.f - detailattenuator + 0.693147f)) - 1.f; //0.69315 = log(2)
        exponent = 1.2f * xlogf(-betemp);
        exponent /= 20.f;
    } else if (detailattenuator >= 0.05f && detailattenuator < 0.25f) {
        const float betemp = expf(-(2.f - detailattenuator + 0.693147f)) - 1.f;
        exponent = 1.2f * xlogf(-betemp);
        exponent /= (-75.f * detailattenuator + 23.75f);
    } else if (detailattenuator >= 0.25f) {
        const float betemp = expf(-(2.f - detailattenuator + 0.693147f)) - 1.f;
        exponent = 1.2f * xlogf(-betemp);
        exponent /= (-2.f * detailattenuator + 5.5f);
    } else {
        exponent = (compression - 1.0f) / 20.f;
    }

    float ap = (thres - 1.f) / (maxp - mean);
    float bp = 1.f - ap * mean;
    ap *= exponent;
    bp *= exponent;

    float a0 = (1.33f * thres - 1.f) / (1.f - mean);
    float b0 = 1.f - a0 * mean;
    a0 *= exponent;
    b0 *= exponent;

    float apn = (thres - 1.f) / (maxN - meanN);
    float bpn = 1.f - apn * meanN;
    apn *= -exponent;
    bpn *= exponent;

    float a0n = (1.33f * thres - 1.f) / (1.f - meanN);
    float b0n = 1.f - a0n * meanN;
    a0n *= -exponent;
    b0n *= exponent;

    madL *= 0.05f;
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#if defined(__SSE2__) || defined(RT_SIMDE)
        const vfloat apv = F2V(ap);
        const vfloat bpv = F2V(bp);
        const vfloat a0v = F2V(a0);
        const vfloat b0v = F2V(b0);
        const vfloat apnv = F2V(apn);
        const vfloat bpnv = F2V(bpn);
        const vfloat a0nv = F2V(a0n);
        const vfloat b0nv = F2V(b0n);
        const vfloat madLv = F2V(madL);
        const vfloat meanv = F2V(mean);
        const vfloat onev = F2V(1.f);
#endif
#ifdef _OPENMP
        #pragma omp for schedule(dynamic,16)
#endif

        for (int y = 0; y < H_L; y++) {
            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

            for (; x < W_L - 3; x += 4) {
                vfloat exponev = onev;
                vfloat valv = LVFU(Source[y][x]);
                const vmask mask1v = vmaskf_ge(valv, ZEROV);
                const vmask mask2v = vmaskf_gt(vself(mask1v, valv, -valv), meanv);
                const vfloat av = vself(mask2v, vself(mask1v, apv, apnv), vself(mask1v, a0v, a0nv));
                const vfloat bv = vself(mask2v, vself(mask1v, bpv, bpnv), vself(mask1v, b0v, b0nv));
                exponev += av * valv + bv;
                valv = vself(mask1v, valv, -valv);
                const vfloat multv = vself(mask1v, onev, -onev);
                const vfloat resultv = multv * xexpf(xlogf(valv + madLv) * exponev);
                STVFU(Source[y][x], resultv);
            }

#endif

            for (; x < W_L; x++) {
                float expone = 1.f;

                if (Source[y][x] >= 0.f) {
                    if (Source[y][x] > mean) {
                        expone += ap * Source[y][x] + bp;
                    } else {
                        expone += a0 * Source[y][x] + b0;
                    }

                    Source[y][x] = xexpf(xlogf(Source[y][x] + madL) * expone);
                } else {
                    if (-Source[y][x] > mean) {
                        expone += apn * Source[y][x] + bpn;
                    } else {
                        expone += a0n * Source[y][x] + b0n;
                    }

                    Source[y][x] = -xexpf(xlogf(-Source[y][x] + madL) * expone);
                }
            }
        }
    }
}

void ImProcFunctions::wavlc(wavelet_decomposition& wdspot, int level_bl, int level_hl, int maxlvl, int level_hr, int level_br, float ahigh, float bhigh, float alow, float blow, float sigmalc, float offslc, float strength, const LocwavCurve & locwavCurve, int numThreads)
{
    float mean[10];
    float meanN[10];
    float sigma[10];
    float sigmaN[10];
    float MaxP[10];
    float MaxN[10];
    float inva5 = 0.8f;
    float inva6 = 0.7f;
    float inva7 = 0.5f;
    float inva8 = 0.4f;
    float inva9 = 0.3f;
    float inva10 = 0.1f;

    Evaluate2(wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);

    for (int dir = 1; dir < 4; dir++) {
        for (int level = level_bl; level < maxlvl; ++level) {
            int W_L = wdspot.level_W(level);
            int H_L = wdspot.level_H(level);
            float klev = 1.f;

            if (level >= level_hl && level <= level_hr) {
                klev = 1.f;
            }

            if (level_hl != level_bl) {
                if (level >= level_bl && level < level_hl) {
                    klev = alow * level + blow;
                }
            }

            if (level_hr != level_br) {
                if (level > level_hr && level <= level_br) {
                    klev = ahigh * level + bhigh;
                }
            }
            if(level_hr < 6) {//low attenuation for low levels
                inva5 = 1.f;
                inva6 = 0.9f;
                inva7 = 0.7f;
                inva8 = 0.6f;
                inva9 = 0.4f;
                inva10 = 0.2f;
            }
            float* const* wav_L = wdspot.level_coeffs(level);
            float offset = offslc;
            if (MaxP[level] > 0.f && mean[level] != 0.f && sigma[level] != 0.f) {
                constexpr float insigma = 0.666f; //SD
                const float logmax = log(MaxP[level]); //log Max
                const float rapX = (offset * mean[level] + sigmalc * sigma[level]) / MaxP[level]; //rapport between sD / max
                const float inx = log(insigma);
                const float iny = log(rapX);
                const float rap = inx / iny; //koef
                const float asig = 0.166f / (sigma[level] * sigmalc);
                const float bsig = 0.5f - asig * (mean[level] * offset);
                const float amean = 0.5f / (mean[level] * offset);
               // const float limit1 = mean[level] + sigmalc * sigma[level];
               // const float limit2 = mean[level];
                const float effect = sigmalc;
                float mea[10];//simulation using mean and sigma, to evaluate signal 
                calceffect(level, mean, sigma, mea, effect, offset);
                float lutFactor;//inva5, inva6, inva7, inva8, inva9, inva10 are define in Contrast profiles.
                float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, inva5, inva6, inva7, inva8, inva9, inva10};//values to give for calculate LUT along signal : minimal near 0 or MaxP
                const auto meaLut = buildMeaLut(inVals, mea, lutFactor);//build LUT
                const float threshold = offset * mean[level] + sigmalc * sigma[level];//base signal calculation.
                
#ifdef _OPENMP
              //  #pragma omp parallel for schedule(dynamic, 16 * W_L) if (multiThread)
                #pragma omp parallel for if (multiThread)

#endif

                for (int y = 0; y < H_L; y++) {
                    for (int x = 0; x < W_L; x++) {//for each pixel
                        float &val = wav_L[dir][y * W_L + x];
                        const float WavCL = std::fabs(wav_L[dir][y * W_L + x]);


                        float absciss;
                        if (WavCL >= threshold) { //for max take into account attenuation response and offset
                            float valcour = xlogf(fabsf(val));
                            float valc = valcour - logmax;
                            float vald = valc * rap;
                                absciss = xexpf(vald);
                        } else if (WavCL >= offset * mean[level]) {//offset only
                            absciss = asig * WavCL + bsig;
                        } else {
                            absciss = amean * WavCL;
                        }
                        const float kc = klev * (locwavCurve[absciss * 500.f] - 0.5f);
                        const float reduceeffect = kc <= 0.f ? 1.f : strength;

                        float kinterm = 1.f + reduceeffect * kc;
                        kinterm = kinterm <= 0.f ? 0.01f : kinterm;

                    //   wav_L[dir][i] *= kinterm <= 0.f ? 0.01f : kinterm;
                        val *= (1.f + (kinterm - 1.f) * (*meaLut)[WavCL * lutFactor]);//change signal (contrast) for each level, direction, with LUT.

                    }
                }
            }
        }
    }

}

void ImProcFunctions::wavcont(const struct local_params& lp, float ** tmp, wavelet_decomposition& wdspot, int level_bl, int maxlvl,
                              const LocwavCurve & loclevwavCurve, bool loclevwavutili,
                              const LocwavCurve & loccompwavCurve, bool loccompwavutili,
                              const LocwavCurve & loccomprewavCurve, bool loccomprewavutili,
                              float radlevblur, int process, float chromablu, float thres,  float sigmadc, float deltad)
{
    //BENCHFUN
    const int W_L = wdspot.level_W(0);
    const int H_L = wdspot.level_H(0);

#ifdef _OPENMP
    const int numThreads = omp_get_max_threads();
#else
    const int numThreads = 1;
#endif
    float mean[10];
    float meanN[10];
    float sigma[10];
    float sigmaN[10];
    float MaxP[10];
    float MaxN[10];
    Evaluate2(wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);

    if (process == 1 && loclevwavCurve && loclevwavutili) { //blur
        array2D<float> templevel(W_L, H_L);

        for (int dir = 1; dir < 4; ++dir) {
            for (int level = level_bl; level < maxlvl; ++level) {
                const auto WavL = wdspot.level_coeffs(level)[dir];
                const float effect = lp.sigmabl;
                constexpr float offs = 1.f;
                float mea[10];
                calceffect(level, mean, sigma, mea, effect, offs);
                float lutFactor;
                const float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, 0.8f, 0.5f, 0.3f, 0.2f, 0.1f, 0.05f};
                const auto meaLut = buildMeaLut(inVals, mea, lutFactor);

                const float klev = 0.25f * loclevwavCurve[level * 55.5f];
                float* src[H_L];

                for (int i = 0; i < H_L; ++i) {
                    src[i] = &wdspot.level_coeffs(level)[dir][i * W_L];
                }

#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
                    gaussianBlur(src, templevel, W_L, H_L, radlevblur * klev * chromablu);
                }
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
#if defined(__SSE2__) || defined(RT_SIMDE)
                    const vfloat lutFactorv = F2V(lutFactor);
#endif
#ifdef _OPENMP
                    #pragma omp for
#endif

                    for (int y = 0; y < H_L; y++) {
                        int x = 0;
                        int j = y * W_L;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x < W_L - 3; x += 4, j += 4) {
                            const vfloat valv = LVFU(WavL[j]);
                            STVFU(WavL[j], intp((*meaLut)[vabsf(valv) * lutFactorv], LVFU(templevel[y][x]), valv));
                        }

#endif

                        for (; x < W_L; x++, j++) {
                            WavL[j] = intp((*meaLut)[std::fabs(WavL[j]) * lutFactor], templevel[y][x], WavL[j]);
                        }
                    }
                }
            }
        }
    } else if (process == 2 && loccompwavCurve && loccompwavutili) { //Directional contrast
        for (int dir = 1; dir < 4; ++dir) {
            for (int level = level_bl; level < maxlvl; ++level) {
                const auto WavL = wdspot.level_coeffs(level)[dir];
                const float effect = sigmadc;
                constexpr float offs = 1.f;
                float mea[10];
                calceffect(level, mean, sigma, mea, effect, offs);
                float lutFactor;
                const float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, 0.8f, 0.7f, 0.5f, 0.3f, 0.2f, 0.1f};
                const auto meaLut = buildMeaLut(inVals, mea, lutFactor);
                const int iteration = deltad;
                const int itplus = 7 + iteration;
                const int itmoins = 7 - iteration;
                const int med = maxlvl / 2;
                int it;

                if (level < med) {
                    it = itmoins;
                } else if (level == med) {
                    it = 7;
                } else {
                    it = itplus;
                }

                const float itf = it;
                const float factor = dir < 3 ? 0.3f : -0.6f;
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
#if defined(__SSE2__) || defined(RT_SIMDE)
                    const vfloat c327d68v = F2V(327.68f);
                    const vfloat factorv = F2V(factor);
                    const vfloat sixv = F2V(6.f);
                    const vfloat zd5v = F2V(0.5f);
                    const vfloat onev = F2V(1.f);
                    const vfloat itfv = F2V(itf);
                    const vfloat lutFactorv = F2V(lutFactor);
#endif
#ifdef _OPENMP
                    #pragma omp for
#endif

                    for (int i = 0; i < H_L; ++i) {
                        int j = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; j < W_L - 3; j += 4) {
                            const vfloat LL100v = LC2VFU(tmp[i * 2][j * 2]) / c327d68v;
                            const vfloat kbav = factorv * (loccompwavCurve[sixv * LL100v] - zd5v); //k1 between 0 and 0.5    0.5==> 1/6=0.16
                            const vfloat valv = LVFU(WavL[i * W_L + j]);
                            STVFU(WavL[i * W_L + j], valv * pow_F(onev + kbav * (*meaLut)[vabsf(valv) * lutFactorv], itfv));
                        }

#endif

                        for (; j < W_L; ++j) {
                            const float LL100 = tmp[i * 2][j * 2] / 327.68f;
                            const float kba = factor * (loccompwavCurve[6.f * LL100] - 0.5f); //k1 between 0 and 0.5    0.5==> 1/6=0.16
                            WavL[i * W_L + j] *= pow_F(1.f + kba * (*meaLut)[std::fabs(WavL[i * W_L + j]) * lutFactor], itf);
                        }
                    }
                }
            }
        }
    } else if (process == 3 && loccomprewavCurve && loccomprewavutili) { //Dynamic compression wavelet
        float madL[10][3];
        array2D<float> templevel(W_L, H_L);
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic) collapse(2) if (multiThread)
#endif

        for (int dir = 1; dir < 4; dir++) {
            for (int level = level_bl; level < maxlvl; ++level) {
                madL[level][dir - 1] = Mad(wdspot.level_coeffs(level)[dir], wdspot.level_W(level) * wdspot.level_H(level)); //evaluate noise by level
            }
        }

        for (int dir = 1; dir < 4; ++dir) {
            for (int level = level_bl; level < maxlvl; ++level) {
                const float effect = lp.sigmadr;
                constexpr float offs = 1.f;
                float mea[10];
                calceffect(level, mean, sigma, mea, effect, offs);
                float lutFactor;
                const float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, 0.8f, 0.65f, 0.5f, 0.4f, 0.25f, 0.1f};
                const auto meaLut = buildMeaLut(inVals, mea, lutFactor);
                const auto wav_L = wdspot.level_coeffs(level)[dir];

#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int y = 0; y < H_L; y++) {
                    for (int x = 0; x < W_L; x++) {
                        int j = y * W_L + x;
                        templevel[y][x] = wav_L[j];
                    }
                }

                float klev = (loccomprewavCurve[level * 55.5f] - 0.75f);

                if (klev < 0.f) {
                    klev *= 2.6666f;//compression increase contraste
                } else {
                    klev *= 4.f;//dilatation reduce contraste - detailattenuator
                }

                const float compression = expf(-klev);
                const float detailattenuator = std::max(klev, 0.f);

                Compresslevels(templevel, W_L, H_L, compression, detailattenuator, thres, mean[level], MaxP[level], meanN[level], MaxN[level], madL[level][dir - 1]);
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
#endif
                {
#if defined(__SSE2__) || defined(RT_SIMDE)
                    const vfloat lutFactorv = F2V(lutFactor);
#endif
#ifdef _OPENMP
                    #pragma omp for
#endif

                    for (int y = 0; y < H_L; y++) {
                        int x = 0;
                        int j = y * W_L;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x < W_L - 3; x += 4, j += 4) {
                            const vfloat valv = LVFU(wav_L[j]);
                            STVFU(wav_L[j], intp((*meaLut)[vabsf(valv) * lutFactorv], LVFU(templevel[y][x]), valv));
                        }

#endif

                        for (; x < W_L; x++, j++) {
                            wav_L[j] = intp((*meaLut)[std::fabs(wav_L[j]) * lutFactor], templevel[y][x], wav_L[j]);
                        }
                    }
                }
            }
        }
    }
}


void ImProcFunctions::wavcontrast4(int call, struct local_params& lp, float ** tmp, float ** tmpa, float ** tmpb, float contrast, float radblur, float radlevblur, int bfw, int bfh, int oW, int oH, int tX, int tY, int tW, int tH, int level_bl, int level_hl, int level_br, int level_hr, int sk, int numThreads,
                                   const LocwavCurve & locwavCurve, bool locwavutili, bool wavcurve, const LocwavCurve& loclevwavCurve, bool loclevwavutili, bool wavcurvelev,
                                   const LocwavCurve & locconwavCurve, bool locconwavutili, bool wavcurvecon,
                                   const LocwavCurve & loccompwavCurve, bool loccompwavutili, bool wavcurvecomp,
                                   const LocwavCurve & loccomprewavCurve, bool loccomprewavutili, bool wavcurvecompre,
                                   const LocwavCurve & locedgwavCurve, bool locedgwavutili,
                                   float sigm, float offs, int & maxlvl, float sigmadc, float deltad, float chromalev, float chromablu, bool blurlc, bool blurena, bool levelena, bool comprena, bool compreena, float compress, float thres, int fw, int fh, int cx, int cy, float ksk, float kskx, float kbh, float kbw)
{
//BENCHFUN
    std::unique_ptr<wavelet_decomposition> wdspot(new wavelet_decomposition(tmp[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

    //first decomposition for compress dynamic range positive values and other process
    if (wdspot->memory_allocation_failed()) {
        return;
    }

    struct grad_params gpwav;

    maxlvl = wdspot->maxlevel();

    int W_Lm = wdspot->level_W(maxlvl - 1); //I assume all decomposition have same W and H

    int H_Lm = wdspot->level_H(maxlvl - 1);

    if (lp.strwav != 0.f && lp.wavgradl) {
        array2D<float> factorwav(W_Lm, H_Lm);
        calclocalGradientParams(call, lp, gpwav, 0, 0, W_Lm, H_Lm, W_Lm, H_Lm, oW, oH, tX, tY, tW, tH, 10, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
        const float mult = lp.strwav < 0.f ? -1.f : 1.f;
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int y = 0; y < H_Lm; y++) {
            for (int x = 0; x < W_Lm; x++) {
                factorwav[y][x] = mult * (1.f - ImProcFunctions::calcGradientFactor(gpwav, cx*kskx + kbw*x, cy*ksk + kbh*y));
            }
        }

        float mean[10];
        float meanN[10];
        float sigma[10];
        float sigmaN[10];
        float MaxP[10];
        float MaxN[10];
        Evaluate2(*wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);
        float alowg = 1.f;
        float blowg = 0.f;

        if (level_hl != level_bl) {
            alowg = 1.f / (level_hl - level_bl);
            blowg = -alowg * level_bl;
        }

        float ahighg = 1.f;
        float bhighg = 0.f;

        if (level_hr != level_br) {
            ahighg = 1.f / (level_hr - level_br);
            bhighg =  -ahighg * level_br;
        }

        for (int dir = 1; dir < 4; dir++) {
            for (int level = level_bl; level < maxlvl; ++level) {
                if (MaxP[level] > 0.f && mean[level] != 0.f && sigma[level] != 0.f) {
                    const int W_L = wdspot->level_W(level);
                    const int H_L = wdspot->level_H(level);
                    auto wav_L = wdspot->level_coeffs(level)[dir];
                    const float effect = lp.sigmalc2;
                    constexpr float offset = 1.f;
                    float mea[10];
                    calceffect(level, mean, sigma, mea, effect, offset);
                    constexpr float insigma = 0.666f; //SD
                    const float logmax = std::log(MaxP[level]); //log Max
                    const float rapX = (mean[level] + lp.sigmalc2 * sigma[level]) / MaxP[level]; //rapport between sD / max
                    const float inx = std::log(insigma);
                    const float iny = std::log(rapX);
                    const float rap = inx / iny; //koef
                    const float asig = 0.166f / (sigma[level] * lp.sigmalc2);
                    const float bsig = 0.5f - asig * mean[level];
                    const float amean = 0.5f / mean[level];
                    float klev = 1.f;

                    if (level_hl != level_bl) {
                        if (level >= level_bl && level < level_hl) {
                            klev = alowg * level + blowg;
                        }
                    }

                    if (level_hr != level_br) {
                        if (level > level_hr && level <= level_br) {
                            klev = ahighg * level + bhighg;
                        }
                    }

                    klev *= 0.8f;
                    const float threshold = mean[level] + lp.sigmalc2 * sigma[level];
                    float lutFactor;
                    const float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, 0.8f, 0.6f, 0.5f, 0.4f, 0.3f, 0.1f};
                    const auto meaLut = buildMeaLut(inVals, mea, lutFactor);

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic, 16) if (multiThread)
#endif

                    for (int y = 0; y < H_L; y++) {
                        for (int x = 0; x < W_L; x++) {
                            const float WavCL = std::fabs(wav_L[y * W_L + x]);

                            float absciss;

                            if (WavCL >= threshold) { //for max
                                absciss = pow_F(WavCL - logmax, rap);
                            } else if (WavCL >= mean[level]) {
                                absciss = asig * WavCL + bsig;
                            } else {
                                absciss = amean * WavCL;
                            }

                            const float kc = klev * factorwav[y][x] * absciss;
                            const float reduceeffect = kc <= 0.f ? 1.f : 1.5f;

                            float kinterm = 1.f + reduceeffect * kc;
                            kinterm = kinterm <= 0.f ? 0.01f : kinterm;
                            wav_L[y * W_L + x] *= (1.f + (kinterm - 1.f) * (*meaLut)[WavCL * lutFactor]);
                        }
                    }
                }
            }
        }
    }

    int W_Level = wdspot->level_W(0);
    int H_Level = wdspot->level_H(0);
    float *wav_L0 = wdspot->get_coeff0();

    if (radblur > 0.f && blurena) {
        float* src[H_Level];

        for (int i = 0; i < H_Level; ++i) {
            src[i] = &wav_L0[i * W_Level];
        }

#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
            gaussianBlur(src, src, W_Level, H_Level, radblur);
        }
    }

    if (compress != 0.f && compreena) {
        const float Compression = expf(-compress);
        const float DetailBoost = std::max(compress, 0.f);

        CompressDR(wav_L0, W_Level, H_Level, Compression, DetailBoost);
    }

    if ((lp.residsha < 0.f || lp.residhi < 0.f)) {
        float tran = 5.f;//transition shadow

        if (lp.residshathr > (100.f - tran)) {
            tran = 100.f - lp.residshathr;
        }

        constexpr float alp = 3.f;
        const float aalp = (1.f - alp) / lp.residshathr;
        const float ath = -lp.residsha / tran;
        const float bth = lp.residsha - ath * lp.residshathr;

        //highlight
        const float tranh = rtengine::min(5.f, lp.residhithr);
        const float athH = lp.residhi / tranh;
        const float bthH = lp.residhi - athH * lp.residhithr;

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < W_Level * H_Level; i++) {
            const float LL100 = wav_L0[i] / 327.68f;

            if (LL100 < lp.residshathr) {
                const float kk = aalp * LL100 + alp;
                wav_L0[i] *= (1.f + kk * lp.residsha / 200.f);
            } else if (LL100 < lp.residshathr + tran) {
                wav_L0[i] *= (1.f + (LL100 * ath + bth) / 200.f);
            }

            if (LL100 > lp.residhithr) {
                wav_L0[i] *= (1.f + lp.residhi / 200.f);
            } else if (LL100 > (lp.residhithr - tranh)) {
                wav_L0[i] *= (1.f + (LL100 * athH + bthH) / 200.f);
            }
        }
    }

    if ((lp.residsha > 0.f || lp.residhi > 0.f)) {
        const std::unique_ptr<LabImage> temp(new LabImage(W_Level, H_Level));
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < H_Level; i++) {
            for (int j = 0; j < W_Level; j++) {
                temp->L[i][j] = wav_L0[i * W_Level + j];
            }
        }

        ImProcFunctions::shadowsHighlights(temp.get(), true, 1, lp.residhi, lp.residsha, 40, sk, lp.residhithr, lp.residshathr);

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < H_Level; i++) {
            for (int j = 0; j < W_Level; j++) {
                wav_L0[i * W_Level + j] = temp->L[i][j];
            }
        }
    }

    if (contrast != 0.f) {
        double avedbl = 0.0; // use double precision for large summations

#ifdef _OPENMP
        #pragma omp parallel for reduction(+:avedbl) if (multiThread)
#endif

        for (int i = 0; i < W_Level * H_Level; i++) {
            avedbl += static_cast<double>(wav_L0[i]);
        }

        const double avg = LIM01(avedbl / (32768.0 * W_Level * H_Level));
        double contreal = 0.6f * contrast;
        DiagonalCurve resid_contrast({
            DCT_NURBS,
            0, 0,
            avg - avg * (0.6 - contreal / 250.0), avg - avg * (0.6 + contreal / 250.0),
            avg + (1. - avg) * (0.6 - contreal / 250.0), avg + (1. - avg) * (0.6 + contreal / 250.0),
            1, 1
        });
#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < W_Level * H_Level; i++) {
            wav_L0[i] = resid_contrast.getVal(LIM01(wav_L0[i] / 32768.f)) * 32768.0;
        }
    }

    float alow = 1.f;
    float blow = 0.f;

    if (level_hl != level_bl) {
        alow = 1.f / (level_hl - level_bl);
        blow = -alow * level_bl;
    }

    float ahigh = 1.f;
    float bhigh = 0.f;

    if (level_hr != level_br) {
        ahigh = 1.f / (level_hr - level_br);
        bhigh =  -ahigh * level_br;
    }

    if (wavcurvelev  || wavcurvecomp  || wavcurvecompre) {//compress dynamic and blur
        if (wavcurvelev && radlevblur > 0.f && blurena) {
            wavcont(lp, tmp, *wdspot, level_bl, maxlvl, loclevwavCurve, loclevwavutili, loccompwavCurve, loccompwavutili, loccomprewavCurve, loccomprewavutili, radlevblur, 1, 1.f, 0.f, 0.f, 0.f);
        }

        if (wavcurvecomp && comprena) {
            wavcont(lp, tmp, *wdspot, level_bl, maxlvl, loclevwavCurve, loclevwavutili, loccompwavCurve, loccompwavutili, loccomprewavCurve, loccomprewavutili, radlevblur, 2, 1.f, 0.f, sigmadc, deltad);
        }

        if (wavcurvecompre && compreena) {
            wavcont(lp, tmp, *wdspot, level_bl, maxlvl, loclevwavCurve, loclevwavutili, loccompwavCurve, loccompwavutili, loccomprewavCurve, loccomprewavutili, radlevblur, 3, 1.f, thres, 0.f, 0.f);
        }
    }

    if (wavcurvecon && levelena) {//contrast  by levels for luminance
        wavcbd(*wdspot, level_bl, maxlvl, locconwavCurve, locconwavutili, sigm, offs, 1.f, sk);
    }

//edge sharpness begin
    if (lp.edgwena && level_bl == 0 && level_br >= 3 && locedgwavCurve && locedgwavutili && lp.strengthw > 0) { //needs the first levels to work!
        float mean[10];
        float meanN[10];
        float sigma[10];
        float sigmaN[10];
        float MaxP[10];
        float MaxN[10];
        Evaluate2(*wdspot, mean, meanN, sigma, sigmaN, MaxP, MaxN, numThreads);
        float edd = 3.f;
        float eddlow = 15.f;
        float eddlipinfl = 0.005f * lp.edgw + 0.4f;
        float eddlipampl = 1.f + lp.basew / 50.f;

        float *koeLi[12];
        float maxkoeLi[12] = {0.f};

        float *koeLibuffer = new float[12 * H_Level * W_Level]; //12

        for (int i = 0; i < 12; i++) {
            koeLi[i] = &koeLibuffer[i * W_Level * H_Level];
        }

        array2D<float> tmC(W_Level, H_Level);

        float gradw = lp.gradw;
        float tloww = lp.tloww;

        for (int lvl = 0; lvl < 4; lvl++) {
            for (int dir = 1; dir < 4; dir++) {
                const int W_L = wdspot->level_W(lvl);
                const int H_L = wdspot->level_H(lvl);
                float* const* wav_L = wdspot->level_coeffs(lvl);
                calckoe(wav_L[dir], gradw, tloww, koeLi[lvl * 3 + dir - 1], lvl, W_L, H_L, edd, maxkoeLi[lvl * 3 + dir - 1], tmC, true);
                // return convolution KoeLi and maxkoeLi of level 0 1 2 3 and Dir Horiz, Vert, Diag
            }
        }

        tmC.free();
        float aamp = 1.f + lp.thigw / 100.f;

        const float alipinfl = (eddlipampl - 1.f) / (1.f - eddlipinfl);
        const float blipinfl = eddlipampl - alipinfl;

        for (int lvl = 0; lvl < 4; lvl++) {
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16)
#endif

            for (int i = 1; i < H_Level - 1; i++) {
                for (int j = 1; j < W_Level - 1; j++) {
                    //treatment of koeLi and maxkoeLi
                    if (lp.lip3) {//Sobel Canny algo improve with parameters
                        // comparison between pixel and neighbors
                        const auto neigh = lp.neiwmet == 1;
                        const auto kneigh = neigh ? 28.f : 38.f;
                        const auto somm = neigh ? 40.f : 50.f;

                        for (int dir = 1; dir < 4; dir++) { //neighbors proxi
                            koeLi[lvl * 3 + dir - 1][i * W_Level + j] = (kneigh * koeLi[lvl * 3 + dir - 1][i * W_Level + j] +
                                    2.f * koeLi[lvl * 3 + dir - 1][(i - 1) * W_Level + j] + 2.f * koeLi[lvl * 3 + dir - 1][(i + 1) * W_Level + j] + 2.f * koeLi[lvl * 3 + dir - 1][i * W_Level + j + 1] + 2.f * koeLi[lvl * 3 + dir - 1][i * W_Level + j - 1]
                                    + koeLi[lvl * 3 + dir - 1][(i - 1) * W_Level + j - 1] + koeLi[lvl * 3 + dir - 1][(i - 1) * W_Level + j + 1] + koeLi[lvl * 3 + dir - 1][(i + 1) * W_Level + j - 1] + koeLi[lvl * 3 + dir - 1][(i + 1) * W_Level + j + 1]) / somm;
                        }
                    }

                    float interm = 0.f;

                    for (int dir = 1; dir < 4; dir++) {
                        //here I evaluate combination of vert / diag / horiz...we are with multiplicators of the signal
                        interm += SQR(koeLi[lvl * 3 + dir - 1][i * W_Level + j]);
                    }

                    interm = std::sqrt(interm) * 0.57736721f;

                    constexpr float eps = 0.0001f;
                    // I think this double ratio (alph, beta) is better than arctg

                    float alph = koeLi[lvl * 3][i * W_Level + j] / (koeLi[lvl * 3 + 1][i * W_Level + j] + eps); //ratio between horizontal and vertical
                    float beta = koeLi[lvl * 3 + 2][i * W_Level + j] / (koeLi[lvl * 3 + 1][i * W_Level + j] + eps); //ratio between diagonal and horizontal

                    //alph evaluate the direction of the gradient regularity Lipschitz
                    // if = 1 we are on an edge
                    // if 0 we are not
                    // we can change and use log..or Arctg but why ?? we can change if need ...
                    //Liamp=1 for eddlipinfl
                    //liamp > 1 for alp >eddlipinfl and alph < 1
                    //Liamp < 1 for alp < eddlipinfl and alph > 0
                    if (alph > 1.f) {
                        alph = 1.f / alph;
                    }

                    if (beta > 1.f) {
                        beta = 1.f / beta;
                    }

                    //take into account diagonal
                    //if in same value OK
                    //if not no edge or reduction
                    float bet = 1.f;

                    if (alph > eddlipinfl && beta < 0.85f * eddlipinfl) { //0.85 arbitrary value ==> eliminate from edge if H V D too different
                        bet = beta;
                    }

                    float kampli;

                    if (alph > eddlipinfl) {
                        kampli = alipinfl * alph + blipinfl; //If beta low reduce kampli
                        kampli = SQR(bet) * kampli * aamp;
                    } else {
                        kampli = SQR(SQR(alph * bet)) / eddlipinfl; //Strong Reduce if beta low
                        kampli = kampli / aamp;
                    }


                    interm *= kampli;

                    if (interm * eddlow < lp.tloww) {
                        interm = 0.01f;    //eliminate too low values
                    }

                    //we can change this part of algo==> not equal but ponderate
                    koeLi[lvl * 3][i * W_Level + j] = koeLi[lvl * 3 + 1][i * W_Level + j] = koeLi[lvl * 3 + 2][i * W_Level + j] = interm; //new value
                    //here KoeLi contains values where gradient is high and coef high, and eliminate low values...
                }
            }
        }

        constexpr float scales[10] = {1.f, 2.f, 4.f, 8.f, 16.f, 32.f, 64.f, 128.f, 256.f, 512.f};
        float scaleskip[10];

        for (int sc = 0; sc < 10; sc++) {
            scaleskip[sc] = scales[sc] / sk;
        }

        const float rad = lp.radiusw / 60.f; //radius ==> not too high value to avoid artifacts
        float value = lp.strengthw / 8.f; //strength

        if (scaleskip[1] < 1.f) {
            constexpr float atten01234 = 0.80f;
            value *= atten01234 * scaleskip[1];    //for zoom < 100% reduce strength...I choose level 1...but!!
        }

        constexpr float lim0 = 20.f; //arbitrary limit for low radius and level between 2 or 3 to 30 maxi
        float repart = lp.detailw;

        if (lp.edgwmet != 1) {
            float brepart;

            if (lp.edgwmet == 0) {
                brepart = 3.f;
            } else { /*if (lp.edgwmet == 2)*/
                brepart = 0.5f;    //arbitrary value to increase / decrease repart, between 1 and 0
            }

            if (rad < lim0 / 60.f) {
                const float arepart = - (brepart - 1.f) / (lim0 / 60.f);
                repart *= arepart * rad + brepart;    //linear repartition of repart
            }
        }

        const float bk = 1.f + repart / 50.f;
        constexpr float al10 = 1.0f; //arbitrary value ==> less = take into account high levels
        const float ak = - (bk - al10) / 10.f; //10 = maximum levels

        for (int lvl = 0; lvl < maxlvl; lvl++) {
            if (MaxP[lvl] > 0.f) { //curve
                const int W_L = wdspot->level_W(lvl);
                const int H_L = wdspot->level_H(lvl);
                float* const* wav_L = wdspot->level_coeffs(lvl);
                const float koef = ak * lvl + bk; //modulate for levels : more levels high, more koef low ==> concentrated action on low levels, without or near for high levels
                float expkoef = -pow_F(std::fabs(rad - lvl), koef);   //reduce effect for high levels

                if (lp.edgwmet == 2) {
                    if (rad < lim0 / 60.f && lvl == 0) {
                        expkoef *= abs(repart);    //reduce effect for low values of rad and level=0==> quasi only level 1 is effective
                    }
                } else if (lp.edgwmet == 0) {
                    if (rad < lim0 / 60.f && lvl == 1) {
                        expkoef /= repart;    //increase effect for low values of rad and level=1==> quasi only level 0 is effective
                    }
                }

                //take into account local contrast
                const float refin = value * xexpf(expkoef);
                const float edgePrecalc = 1.f + refin; //estimate edge "pseudo variance"
                constexpr float insigma = 0.666f; //SD
                const float logmax = xlogf(MaxP[lvl]);  //log Max
                const float rapX = (mean[lvl] + sigma[lvl]) / MaxP[lvl]; //rapport between sD / max
                const float inx = xlogf(insigma);
                const float iny = xlogf(rapX);
                const float rap = inx / iny; //koef
                const float asig = 0.166f / sigma[lvl];
                const float bsig = 0.5f - asig * mean[lvl];
                const float amean = 0.5f / mean[lvl];
                constexpr int borderL = 1;
                constexpr float abssd = 4.f; //amplification reference
                constexpr float bbssd = 2.f; //mini ampli
                constexpr float maxamp = 2.5f; //maxi ampli at end
                constexpr float maxampd = 10.f; //maxi ampli at end
                constexpr float a_abssd = (maxamp - abssd) / 0.333f;
                constexpr float b_abssd = maxamp - a_abssd;
                constexpr float da_abssd = (maxampd - abssd) / 0.333f;
                constexpr float db_abssd = maxampd - da_abssd;
                constexpr float am = (abssd - bbssd) / 0.666f;
                const float effect = lp.sigmaed;
                constexpr float offset = 1.f;
                float mea[10];
                calceffect(lvl, mean, sigma, mea, effect, offset);
                float lutFactor;
                const float inVals[] = {0.05f, 0.2f, 0.7f, 1.f, 1.f, 0.8f, 0.5f, 0.3f, 0.2f, 0.1f, 0.05f};
                const auto meaLut = buildMeaLut(inVals, mea, lutFactor);

                for (int dir = 1; dir < 4; dir++) {
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic, 16) if(multiThread)
#endif

                    for (int i = borderL; i < H_L - borderL; i++) {
                        for (int j = borderL; j < W_L - borderL; j++) {
                            const int k = i * W_L + j;

                            float edge;

                            if (lvl < 4) {
                                edge = 1.f + (edgePrecalc - 1.f) * (koeLi[lvl * 3][k]) / (1.f + 0.9f * maxkoeLi[lvl * 3 + dir - 1]);
                            } else {
                                edge = edgePrecalc;
                            }

                            float absciss = 0.f;

                            if (std::fabs(wav_L[dir][k]) >= mean[lvl] + sigma[lvl]) {  //for max
                                absciss = xexpf((xlogf(std::fabs(wav_L[dir][k])) - logmax) * rap);
                            } else if (std::fabs(wav_L[dir][k]) >= mean[lvl]) {
                                absciss = asig * std::fabs(wav_L[dir][k]) + bsig;
                            } else { /*if (std::fabs(wav_L[dir][k]) < mean[lvl])*/
                                absciss = amean * std::fabs(wav_L[dir][k]);
                            }

                            // Threshold adjuster settings==> approximative for curve
                            //kmul about average cbrt(3--40 / 10)==>1.5 to 2.5
                            //kmul about SD   10--60  / 35 ==> 2
                            // kmul about low  cbrt((5.f+cp.edg_low)/5.f);==> 1.5
                            // kmul about max ==> 9
                            // we can change these values
                            // result is different not best or bad than threshold slider...but similar
                            float kmul;
                            float kmuld;

                            if (absciss > 0.666f && absciss < 1.f) {
                                kmul = a_abssd * absciss + b_abssd;    //about max  ==> kinterm
                                kmuld = da_abssd * absciss + db_abssd;
                            } else {
                                kmul = kmuld = absciss * am + bbssd;
                            }

                            const float kc = kmul * (locedgwavCurve[absciss * 500.f] - 0.5f);

                            float kinterm;

                            if (kc >= 0.f) {
                                constexpr float reduceeffect = 0.6f;
                                kinterm = 1.f + reduceeffect * kc;    //about 1 to 3 general and big amplification for max (under 0)
                            } else {
                                const float kcd = kmuld * (locedgwavCurve[absciss * 500.f] - 0.5f);
                                kinterm = 1.f - SQR(kcd) / 10.f;
                            }

                            if (kinterm < 0.f) {
                                kinterm = 0.01f;
                            }

                            edge = std::max(edge * kinterm, 1.f);
                            wav_L[dir][k] *= 1.f + (edge - 1.f) * (*meaLut)[std::fabs(wav_L[dir][k]) * lutFactor];
                        }
                    }
                }
            }
        }

        if (koeLibuffer) {
            delete [] koeLibuffer;
        }
    }

//edge sharpness end

    if (locwavCurve && locwavutili && wavcurve) {//simple local contrast in function luminance
        float strengthlc = 1.5f;
        wavlc(*wdspot, level_bl, level_hl, maxlvl, level_hr, level_br, ahigh, bhigh, alow, blow, lp.sigmalc, lp.offslc, strengthlc, locwavCurve, numThreads);
    }

    //reconstruct all for L
    wdspot->reconstruct(tmp[0], 1.f);

    bool reconstruct = false;

    if (wavcurvecon && (chromalev != 1.f) && levelena) { // a if need ) {//contrast by levels for chroma a
        // a
        wdspot.reset(new wavelet_decomposition(tmpa[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

        if (wdspot->memory_allocation_failed()) {
            return;
        }

        wavcbd(*wdspot, level_bl, maxlvl, locconwavCurve, locconwavutili, sigm, offs, chromalev, sk);
        reconstruct = true;
    }

    if (wavcurvelev && radlevblur > 0.f && blurena && chromablu > 0.f && !blurlc) {//chroma blur if need
        // a
        if (!reconstruct) {
            wdspot.reset(new wavelet_decomposition(tmpa[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

            if (wdspot->memory_allocation_failed()) {
                return;
            }
        }

        wavcont(lp, tmp, *wdspot, level_bl, maxlvl, loclevwavCurve, loclevwavutili, loccompwavCurve, loccompwavutili, loccomprewavCurve, loccomprewavutili, radlevblur, 1, chromablu, 0.f, 0.f, 0.f);
        reconstruct = true;
    }

    if (reconstruct) {
        wdspot->reconstruct(tmpa[0], 1.f);
    }

    reconstruct = false;

    if (wavcurvecon && (chromalev != 1.f) && levelena) { // b if need ) {//contrast by levels for chroma b
        //b
        wdspot.reset(new wavelet_decomposition(tmpb[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

        if (wdspot->memory_allocation_failed()) {
            return;
        }

        //b
        wavcbd(*wdspot, level_bl, maxlvl, locconwavCurve, locconwavutili, sigm, offs, chromalev, sk);
        reconstruct = true;
    }

    if (wavcurvelev && radlevblur > 0.f && blurena && chromablu > 0.f && !blurlc) {//chroma blur if need
        //b
        if (!reconstruct) {
            wdspot.reset(new wavelet_decomposition(tmpb[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

            if (wdspot->memory_allocation_failed()) {
                return;
            }
        }

        wavcont(lp, tmp, *wdspot, level_bl, maxlvl, loclevwavCurve, loclevwavutili, loccompwavCurve, loccompwavutili, loccomprewavCurve, loccomprewavutili, radlevblur, 1, chromablu, 0.f, 0.f, 0.f);
        reconstruct = true;
    }

    if (reconstruct) {
        wdspot->reconstruct(tmpb[0], 1.f);
    }


    //gamma and slope residual image - be careful memory
    bool tonecur = false;
    const Glib::ustring profile = params->icm.workingProfile;
    bool isworking = (profile == "sRGB" || profile == "Adobe RGB" || profile == "ProPhoto" || profile == "WideGamut" || profile == "BruceRGB" || profile == "Beta RGB" || profile == "BestRGB" || profile == "Rec2020" || profile == "ACESp0" || profile == "ACESp1");

    if (isworking && (lp.residgam != 2.4f || lp.residslop != 12.92f)) {
        tonecur = true;
    }

    if (tonecur) {
        std::unique_ptr<wavelet_decomposition> wdspotL(new wavelet_decomposition(tmp[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

        if (wdspotL->memory_allocation_failed()) {
            return;
        }

        std::unique_ptr<wavelet_decomposition> wdspota(new wavelet_decomposition(tmpa[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

        if (wdspota->memory_allocation_failed()) {
            return;
        }

        std::unique_ptr<wavelet_decomposition> wdspotb(new wavelet_decomposition(tmpb[0], bfw, bfh, maxlvl, 1, sk, numThreads, lp.daubLen));

        if (wdspotb->memory_allocation_failed()) {
            return;
        }

        int W_Level = wdspotL->level_W(0);
        int H_Level = wdspotL->level_H(0);
        float *wav_L0 = wdspotL->get_coeff0();
        float *wav_a0 = wdspota->get_coeff0();
        float *wav_b0 = wdspotb->get_coeff0();

        const std::unique_ptr<LabImage> labresid(new LabImage(W_Level, H_Level));
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < H_Level; y++) {
            for (int x = 0; x < W_Level; x++) {
                labresid->L[y][x] = wav_L0[y * W_Level + x];
                labresid->a[y][x] = wav_a0[y * W_Level + x];
                labresid->b[y][x] = wav_b0[y * W_Level + x];
            }
        }

        Imagefloat *tmpImage = nullptr;
        tmpImage = new Imagefloat(W_Level, H_Level);
        lab2rgb(*labresid, *tmpImage, params->icm.workingProfile);
        Glib::ustring prof = params->icm.workingProfile;
        cmsHTRANSFORM dummy = nullptr;
        int ill = 0;
        int locprim = 0;
        float rdx, rdy, grx, gry, blx, bly = 0.f;
        float meanx, meany, meanxe, meanye, maxdat = 0.f;
        double p[6] = {0., 0., 0., 0., 0., 0.};
        workingtrc(0, tmpImage, tmpImage, W_Level, H_Level, -5, prof, 2.4, 12.92310, 0, ill, 0, 0, rdx, rdy, grx, gry, blx, bly ,meanx, meany, meanxe, meanye, maxdat, p, dummy, true, false, false, false);
        workingtrc(0, tmpImage, tmpImage, W_Level, H_Level, 1, prof, lp.residgam, lp.residslop, 0, ill, 0, locprim, rdx, rdy, grx, gry, blx, bly , meanx, meany, meanxe, meanye, maxdat, p, dummy, false, true, true, false);//be careful no gamut control
        rgb2lab(*tmpImage, *labresid, params->icm.workingProfile);
        delete tmpImage;

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < H_Level; y++) {
            for (int x = 0; x < W_Level; x++) {
                wav_L0[y * W_Level + x] = labresid->L[y][x];
                wav_a0[y * W_Level + x] = labresid->a[y][x];
                wav_b0[y * W_Level + x] = labresid->b[y][x];
            }
        }

        wdspotL->reconstruct(tmp[0], 1.f);
        wdspota->reconstruct(tmpa[0], 1.f);
        wdspotb->reconstruct(tmpb[0], 1.f);
    }
}


void ImProcFunctions::fftw_denoise(int sk, int GW, int GH, int max_numblox_W, int min_numblox_W, float **tmp1, array2D<float> *Lin, int numThreads, const struct local_params & lp, int chrom)
{
    // BENCHFUN

    fftwf_plan plan_forward_blox[2];
    fftwf_plan plan_backward_blox[2];

    array2D<float> tilemask_in(TS, TS);
    array2D<float> tilemask_out(TS, TS);

    float *Lbloxtmp  = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * TS * TS * sizeof(float)));
    float *fLbloxtmp = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * TS * TS * sizeof(float)));
    float params_Ldetail = 0.f;

    int nfwd[2] = {TS, TS};

    //for DCT:
    fftw_r2r_kind fwdkind[2] = {FFTW_REDFT10, FFTW_REDFT10};
    fftw_r2r_kind bwdkind[2] = {FFTW_REDFT01, FFTW_REDFT01};

    // Creating the plans with FFTW_MEASURE instead of FFTW_ESTIMATE speeds up the execute a bit
    plan_forward_blox[0]  = fftwf_plan_many_r2r(2, nfwd, max_numblox_W, Lbloxtmp, nullptr, 1, TS * TS, fLbloxtmp, nullptr, 1, TS * TS, fwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_backward_blox[0] = fftwf_plan_many_r2r(2, nfwd, max_numblox_W, fLbloxtmp, nullptr, 1, TS * TS, Lbloxtmp, nullptr, 1, TS * TS, bwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_forward_blox[1]  = fftwf_plan_many_r2r(2, nfwd, min_numblox_W, Lbloxtmp, nullptr, 1, TS * TS, fLbloxtmp, nullptr, 1, TS * TS, fwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    plan_backward_blox[1] = fftwf_plan_many_r2r(2, nfwd, min_numblox_W, fLbloxtmp, nullptr, 1, TS * TS, Lbloxtmp, nullptr, 1, TS * TS, bwdkind, FFTW_MEASURE | FFTW_DESTROY_INPUT);
    fftwf_free(Lbloxtmp);
    fftwf_free(fLbloxtmp);
    const int border = rtengine::max(2, TS / 16);

    for (int i = 0; i < TS; ++i) {
        float i1 = abs((i > TS / 2 ? i - TS + 1 : i));
        float vmask = (i1 < border ? SQR(sin((rtengine::RT_PI_F * i1) / (2 * border))) : 1.0f);
        float vmask2 = (i1 < 2 * border ? SQR(sin((rtengine::RT_PI_F * i1) / (2 * border))) : 1.0f);

        for (int j = 0; j < TS; ++j) {
            float j1 = abs((j > TS / 2 ? j - TS + 1 : j));
            tilemask_in[i][j] = (vmask * (j1 < border ? SQR(sin((rtengine::RT_PI_F * j1) / (2 * border))) : 1.0f)) + epsilonw;
            tilemask_out[i][j] = (vmask2 * (j1 < 2 * border ? SQR(sin((rtengine::RT_PI_F * j1) / (2 * border))) : 1.0f)) + epsilonw;

        }
    }


    float *LbloxArray[numThreads];
    float *fLbloxArray[numThreads];



    const int numblox_W = ceil((static_cast<float>(GW)) / offset) + 2;
    const int numblox_H = ceil((static_cast<float>(GH)) / offset) + 2;


    //residual between input and denoised L channel
    array2D<float> Ldetail(GW, GH, ARRAY2D_CLEAR_DATA);
    array2D<float> totwt(GW, GH, ARRAY2D_CLEAR_DATA); //weight for combining DCT blocks
    array2D<float> prov(GW, GH, ARRAY2D_CLEAR_DATA);

    for (int i = 0; i < numThreads; ++i) {
        LbloxArray[i]  = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * TS * TS * sizeof(float)));
        fLbloxArray[i] = reinterpret_cast<float*>(fftwf_malloc(max_numblox_W * TS * TS * sizeof(float)));
    }

#ifdef _OPENMP
    int masterThread = omp_get_thread_num();
#endif
#ifdef _OPENMP
    #pragma omp parallel if (multiThread)
#endif
    {
#ifdef _OPENMP
        int subThread = masterThread * 1 + omp_get_thread_num();
#else
        int subThread = 0;
#endif
        float *Lblox = LbloxArray[subThread];
        float *fLblox = fLbloxArray[subThread];
        float pBuf[GW + TS + 2 * offset] ALIGNED16;
#ifdef _OPENMP
        #pragma omp for
#endif

        for (int vblk = 0; vblk < numblox_H; ++vblk) {

            int top = (vblk - 1) * offset;
            float * datarow = pBuf + offset;

            for (int i = 0; i < TS; ++i) {
                int row = top + i;
                int rr = row;

                if (row < 0) {
                    rr = rtengine::min(-row, GH - 1);
                } else if (row >= GH) {
                    rr = rtengine::max(0, 2 * GH - 2 - row);
                }

                for (int j = 0; j < GW; ++j) {
                    datarow[j] = ((*Lin)[rr][j] - tmp1[rr][j]);
                    prov[rr][j] = std::fabs(tmp1[rr][j]);

                }

                for (int j = -1 * offset; j < 0; ++j) {
                    datarow[j] = datarow[rtengine::min(-j, GW - 1)];
                }

                for (int j = GW; j < GW + TS + offset; ++j) {
                    datarow[j] = datarow[rtengine::max(0, 2 * GW - 2 - j)];
                }//now we have a padded data row

                //now fill this row of the blocks with Lab high pass data
                for (int hblk = 0; hblk < numblox_W; ++hblk) {
                    int left = (hblk - 1) * offset;
                    int indx = (hblk) * TS; //index of block in malloc

                    if (top + i >= 0 && top + i < GH) {
                        int j;

                        for (j = 0; j < rtengine::min((-left), TS); ++j) {
                            Lblox[(indx + i)*TS + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }

                        for (; j < rtengine::min(TS, GW - left); ++j) {
                            Lblox[(indx + i)*TS + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                            totwt[top + i][left + j] += tilemask_in[i][j] * tilemask_out[i][j];
                        }

                        for (; j < TS; ++j) {
                            Lblox[(indx + i)*TS + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }
                    } else {
                        for (int j = 0; j < TS; ++j) {
                            Lblox[(indx + i)*TS + j] = tilemask_in[i][j] * datarow[left + j]; // luma data
                        }
                    }

                }

            }//end of filling block row

            //fftwf_print_plan (plan_forward_blox);
            if (numblox_W == max_numblox_W) {
                fftwf_execute_r2r(plan_forward_blox[0], Lblox, fLblox);    // DCT an entire row of tiles
            } else {
                fftwf_execute_r2r(plan_forward_blox[1], Lblox, fLblox);    // DCT an entire row of tiles
            }

            // now process the vblk row of blocks for noise reduction

            float noisevar_Ldetail = 1.f;

            if (chrom == 0) {
                params_Ldetail = rtengine::min(float(lp.noiseldetail), 99.9f);    // max out to avoid div by zero when using noisevar_Ldetail as divisor
                noisevar_Ldetail = SQR(static_cast<float>(SQR(100.f - params_Ldetail) + 50.f * (100.f - params_Ldetail)) * TS * 0.5f);
            } else if (chrom == 1) {
                params_Ldetail = rtengine::min(float(lp.noisechrodetail), 99.9f);
                noisevar_Ldetail = 100.f * rtengine::SQR((static_cast<float>(SQR(100.f - params_Ldetail)) * TS * 0.5f));//to test ???
            }

            for (int hblk = 0; hblk < numblox_W; ++hblk) {
                ImProcFunctions::RGBtile_denoise(fLblox, hblk, noisevar_Ldetail);

            }//end of horizontal block loop

            //now perform inverse FT of an entire row of blocks
            if (numblox_W == max_numblox_W) {
                fftwf_execute_r2r(plan_backward_blox[0], fLblox, Lblox);    //for DCT
            } else {
                fftwf_execute_r2r(plan_backward_blox[1], fLblox, Lblox);    //for DCT
            }

            int topproc = (vblk - 1) * offset;

            //add row of blocks to output image tile
            ImProcFunctions::RGBoutput_tile_row(Lblox, Ldetail, tilemask_out, GH, GW, topproc);

        }//end of vertical block loop
    }

    //Threshold DCT from Alberto Grigio, adapted to Rawtherapee
    const int detail_thresh = lp.detailthr;
    array2D<float> mask;

    if (detail_thresh > 0) {
        mask(GW, GH);

        if (lp.usemask) {//with Laplacian
            float amount = LIM01(float(detail_thresh) / 100.f);
            float thr = (1.f - amount);
            float alph = params_Ldetail / 100.f;
            array2D<float> LL(GW, GH, prov, ARRAY2D_BYREFERENCE);
            laplacian(LL, mask, GW, GH, 25.f, 20000.f, amount, false);

            for (int i = 0; i < GH; ++i) {
                for (int j = 0; j < GW; ++j) {
                    mask[i][j] = LIM01(mask[i][j] + thr);
                }
            }

            for (int i = 0; i < 3; ++i) {
                boxblur(static_cast<float**>(mask), static_cast<float**>(mask), 10 / sk, GW, GH, false);

            }

            for (int i = 0; i < GH; ++i) {
                for (int j = 0; j < GW; ++j) {
                    float k = 1.f - mask[i][j] * alph;
                    mask[i][j] = 1.f - (k * k);
                }
            }
        } else {//with blend mask
            float thr = log2lin(float(detail_thresh) / 200.f, 100.f);
            buildBlendMask(prov, mask, GW, GH, thr);
#ifdef _OPENMP
            #pragma omp parallel if (multiThread)
#endif
            {
                gaussianBlur(mask, mask, GW, GH, 20.0 / sk);
            }
            array2D<float> m2(GW, GH);
            constexpr float alfa = 0.856f;
            const float beta = 1.f + std::sqrt(log2lin(thr, 100.f));
            buildGradientsMask(GW, GH, prov, m2, params_Ldetail / 100.f, 7, 3, alfa, beta, multiThread);

            for (int i = 0; i < GH; ++i) {
                for (int j = 0; j < GW; ++j) {
                    mask[i][j] *= m2[i][j];
                }
            }
        }
    }


#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int i = 0; i < GH; ++i) {
        for (int j = 0; j < GW; ++j) {
            float d = Ldetail[i][j] / totwt[i][j];

            if (detail_thresh > 0) {
                d *= mask[i][j];
            }

            //may want to include masking threshold for large hipass data to preserve edges/detail
            tmp1[i][j] += d;
        }
    }

    mask.free();
//end Threshold DCT


    delete Lin;


    for (int i = 0; i < numThreads; ++i) {
        fftwf_free(LbloxArray[i]);
        fftwf_free(fLbloxArray[i]);
    }

    fftwf_destroy_plan(plan_forward_blox[0]);
    fftwf_destroy_plan(plan_backward_blox[0]);
    fftwf_destroy_plan(plan_forward_blox[1]);
    fftwf_destroy_plan(plan_backward_blox[1]);
    fftwf_cleanup();


}

void ImProcFunctions::recovm(float highrec, float lowrec, float thrrec, bool invrec, int bfw, int bfh, int xstart, int ystart, float decay, int sk,  LabImage *bufmaskblurbl, LabImage *tmp1, LabImage *tmp3, bool multiThread)
{
    // differences with makrecov (tmp3,...) and ImProcFunctions to allow used elsewhere
    // possibility (to do) to change algorithm for Blur functions : transition blow, highlow, decay,...and use of masklum...
    array2D<float> masklum;
    masklum(bfw, bfh);

    for (int ir = 0; ir < bfh; ir++)
        for (int jr = 0; jr < bfw; jr++) {
            masklum[ir][jr] = 1.f;
        }

    float hig = highrec;
    float higc;
    calcdif(hig, higc);
    float low = lowrec;
    float lowc;
    calcdif(low, lowc);

    if (higc < lowc) {
        higc = lowc + 0.01f;
    }

    float th = (thrrec - 1.f);
    float ahigh = th / (higc - 100.f);
    float bhigh = 1.f - higc * ahigh;

    float alow = th / lowc;
    float blow = 1.f - th;

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++)
        for (int jr = 0; jr < bfw; jr++) {
            const float lM = bufmaskblurbl->L[ir + ystart][jr + xstart];
            const float lmr = lM / 327.68f;

            if (lM < 327.68f * lowc) {
                masklum[ir][jr] = alow * lmr + blow;
            } else if (lM < 327.68f * higc) {
                //possible changes here.
            } else {
                masklum[ir][jr] = ahigh * lmr + bhigh;
            }
            
            float k = masklum[ir][jr];

            if (invrec == true) {
                masklum[ir][jr] = 1.f - pow(k, decay);
            } else {
                masklum[ir][jr] = pow(k, decay);
            }
                
        }

        for (int i = 0; i < 3; ++i) {
            boxblur(static_cast<float**>(masklum), static_cast<float**>(masklum), 10 / sk, bfw, bfh, false);
        }

#ifdef _OPENMP
        #pragma omp parallel for if (multiThread)
#endif

        for (int i = 0; i < bfh; ++i) {
            for (int j = 0; j < bfw; ++j) {
                tmp1->L[i][j] = (tmp3->L[i][j] - tmp1->L[i][j]) *  LIM01(masklum[i][j]) + tmp1->L[i][j];
                tmp1->a[i][j] = (tmp3->a[i][j] - tmp1->a[i][j]) *  LIM01(masklum[i][j]) + tmp1->a[i][j];
                tmp1->b[i][j] = (tmp3->b[i][j] - tmp1->b[i][j]) *  LIM01(masklum[i][j]) + tmp1->b[i][j];
            }
        }

        masklum.free();
    
}


void ImProcFunctions::DeNoise(int sp, int call, int aut,  bool noiscfactiv, const struct local_params & lp, LabImage * originalmaskbl, LabImage *  bufmaskblurbl, int levred, float huerefblur, float lumarefblur, float chromarefblur, LabImage * original, LabImage * transformed,
    int cx, int cy, int sk, const LocwavCurve& locwavCurvehue, bool locwavhueutili, const LocwavCurve& locwavCurvehuecont, bool locwavhueutilicont, float *resi, float &denocont, float *savmadl)
{
   // BENCHFUN
//local denoise
    //all these variables are to prevent use of denoise when non necessary
    // but with qualmet = 2 (default for best quality) we must denoise chroma with little values to prevent artifacts due to variations of Hue
    // but if user select voluntary denoise, it is that choice the good (priority)
    bool execcolor = (lp.chro != 0.f || lp.ligh != 0.f || lp.cont != 0); // only if one slider or more is engaged
    bool execbdl = (lp.mulloc[0] != 1.f || lp.mulloc[1] != 1.f || lp.mulloc[2] != 1.f || lp.mulloc[3] != 1.f || lp.mulloc[4] != 1.f || lp.mulloc[5] != 1.f) ;//only if user want cbdl
    bool execdenoi = noiscfactiv && ((lp.colorena && execcolor) || (lp.tonemapena && lp.strengt != 0.f) || (lp.cbdlena && execbdl) || (lp.sfena && lp.strng > 0.f) || (lp.lcena && lp.lcamount > 0.f) || (lp.sharpena && lp.shrad > 0.42) || (lp.retiena && lp.str > 0.f)  || (lp.exposena && lp.expcomp != 0.f)  || (lp.expvib && lp.past != 0.f));
    bool execmaskden = (lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 3 || lp.showmaskblmet == 4) && lp.smasktyp != 0;

//    const int ys = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
//    const int ye = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
//    const int xs = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
//    const int xe = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
//    const int hspot = ye - ys;
//    const int wspot = xe - xs;

   if (((lp.noiself > 0.f || lp.noiself0 > 0.f || lp.noiself2 > 0.f || lp.nlstr > 0 || lp.wavcurvedenoi || lp.noiselc > 0.f || lp.noisecf > 0.f || lp.noisecc > 0.f
            || execmaskden || aut == 1 || aut == 2) && lp.denoiena && lp.quamet != 3) || execdenoi) {  // sk == 1 ??

        //StopWatch Stop1("locallab Denoise called");

        if (aut == 0) {
            MyMutex::MyLock lock(*fftwMutex);
        }

        if (lp.noisecf >= 0.01f || lp.noisecc >= 0.01f || aut == 1 || aut == 2) {
            noiscfactiv = false;
            levred = 7;
        }

        int GW = transformed->W;
        int GH = transformed->H;

        bool HHhuecurve = false;

        if (locwavCurvehue && locwavhueutili) {
            for (int i = 0; i < 500; i++) {
                if (locwavCurvehue[i] != 0.5f) {
                    HHhuecurve = true;
                    break;
                }
            }
        }
        bool HHhuecurvecont = false;

        if (locwavCurvehuecont && locwavhueutilicont) {
            for (int i = 0; i < 500; i++) {
                if (locwavCurvehuecont[i] != 0.5f) {
                    HHhuecurvecont = true;
                    break;
                }
            }
        }


#ifdef _OPENMP
        const int numThreads = omp_get_max_threads();
#else
        const int numThreads = 1;

#endif
        int minwin = rtengine::min(GW, GH);
        int maxlevelspot = 9;//maximum possible
        bool isnois = true;

        // adap maximum level wavelet to size of crop
        while ((1 << maxlevelspot) >= (minwin) && maxlevelspot  > 1) {
            --maxlevelspot ;
        }

        levred = rtengine::min(levred, maxlevelspot);

        if (levred < 7) { //If windows preview or detail window too small exit to avoid artifacts
            isnois = false;

            if (lp.quamet == 2) {
                isnois = true;
            }
        }


        
        if (call == 1 && ((GW >= mDEN && GH >= mDEN  && isnois) || lp.quamet == 2)) {


            LabImage tmp1(transformed->W, transformed->H);
            LabImage tmp2(transformed->W, transformed->H);
            tmp2.clear();
            LabImage tmp4(transformed->W, transformed->H);//for contrast threshold keep old values
            tmp4.clear();

            array2D<float> *Lin = nullptr;
            array2D<float> *Ain = nullptr;
            array2D<float> *Bin = nullptr;

            int max_numblox_W = ceil((static_cast<float>(GW)) / offset) + 2;
            // calculate min size of numblox_W.
            int min_numblox_W = ceil((static_cast<float>(GW)) / offset) + 2;

#ifdef _OPENMP
                    #pragma omp parallel for
#endif
            for (int ir = 0; ir < GH; ir++)
                for (int jr = 0; jr < GW; jr++) {
                    tmp1.L[ir][jr] = original->L[ir][jr];
                    tmp1.a[ir][jr] = original->a[ir][jr];
                    tmp1.b[ir][jr] = original->b[ir][jr];
                    
                    tmp4.L[ir][jr] = original->L[ir][jr];
                    tmp4.a[ir][jr] = original->a[ir][jr];
                    tmp4.b[ir][jr] = original->b[ir][jr];
                }
            
            if(lp.nlstr > 0) {
                int iter = lp.nliter;//iterations Nlmeans
                for(int it = 0; it < iter; it++) {
                    NLMeans(tmp1.L, lp.nlstr, lp.nldet, lp.nlpat, lp.nlrad, lp.nlgam, GW, GH, float (sk), multiThread);
                }
            }

            float gamma = lp.noisegam;
            rtengine::GammaValues g_a; //gamma parameters
            double pwr = 1.0 / (double) lp.noisegam;//default 3.0 - gamma Lab
            double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
            rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

            if (gamma > 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < GH; ++y) {
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < GW - 3; x += 4) {
                        STVFU(tmp1.L[y][x], F2V(32768.f) * igammalog(LVFU(tmp1.L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[2]), F2V(g_a[4])));
                    }

#endif

                    for (; x < GW; ++x) {
                        tmp1.L[y][x] = 32768.f * igammalog(tmp1.L[y][x] / 32768.f, gamma, ts, g_a[2], g_a[4]);
                    }
                }
            }

            //  int DaubLen = 6;

            int levwavL = levred;
            int skip = 1;

            wavelet_decomposition Ldecomp(tmp1.L[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);
            wavelet_decomposition adecomp(tmp1.a[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);
            wavelet_decomposition bdecomp(tmp1.b[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);

            float madL[10][3];
            int edge = 2;
            if (!Ldecomp.memory_allocation_failed()) {
#ifdef _OPENMP
               #pragma omp parallel for schedule(dynamic) collapse(2) if (multiThread)
#endif

                for (int lvl = 0; lvl < levred; lvl++) {
                    for (int dir = 1; dir < 4; dir++) {
                        int Wlvl_L = Ldecomp.level_W(lvl);
                        int Hlvl_L = Ldecomp.level_H(lvl);
                        const float* const* WavCoeffs_L = Ldecomp.level_coeffs(lvl);
                        madL[lvl][dir - 1] = SQR(Mad(WavCoeffs_L[dir], Wlvl_L * Hlvl_L));
                    }
                }
                
                if (params->locallab.spots.at(sp).lockmadl) {
                    for (int lvl = 0; lvl < levred; lvl++) {
                        for (int dir = 1; dir < 4; dir++) {
                            savmadl[lvl +  (dir - 1) * 7] = madL[lvl][dir - 1];//save current MadL information 
                             
                            madL[lvl][dir - 1] = params->locallab.spots.at(sp).madlsav[lvl +  (dir - 1) * 7];//retrieve Madl in procparams
                        }
                    }  
                }
                    if (settings->verbose) {
                        for (int lvl = 0; lvl < levred; lvl++) {
                            for (int dir = 1; dir < 4; dir++) {
                                 printf("Preview level=%i dir=%i madL=%6.0f\n", lvl, dir-1, (double) madL[lvl][dir-1]);                               
                            }
                        }
                    }
                
                float vari[levred];
                float mxsl = 0.f;
                //      float mxsfl = 0.f;

                edge = 2;
                vari[0] = 0.8f * SQR((lp.noiself0 / 125.f) * (1.f + lp.noiself0 / 25.f));
                vari[1] = 0.8f * SQR((lp.noiself / 125.f) * (1.f + lp.noiself / 25.f));
                vari[2] = 0.8f * SQR((lp.noiself2 / 125.f) * (1.f + lp.noiself2 / 25.f));

                vari[3] = 0.8f * SQR((lp.noiselc / 125.f) * (1.f + lp.noiselc / 25.f));
                vari[4] = 1.f * SQR((lp.noiselc4 / 125.f) * (1.f + lp.noiselc4 / 25.f));
                vari[5] = 1.5f * SQR((lp.noiselc5 / 125.f) * (1.f + lp.noiselc5 / 25.f));
                vari[6] = 2.5f * SQR((lp.noiselc6 / 125.f) * (1.f + lp.noiselc6 / 25.f));

                {
                    float kr3 = 0.f;

                        if (lp.noiselc < 30.f) {
                            kr3 = 0.f;
                        } else if (lp.noiselc < 50.f) {
                            kr3 = 0.5f;
                        } else if (lp.noiselc < 70.f) {
                            kr3 = 0.7f;
                        } else {
                            kr3 = 1.f;
                        }

                    vari[0] = rtengine::max(0.000001f, vari[0]);
                    vari[1] = rtengine::max(0.000001f, vari[1]);
                    vari[2] = rtengine::max(0.000001f, vari[2]);
                    vari[3] = rtengine::max(0.000001f, kr3 * vari[3]);
                    vari[4] = rtengine::max(0.000001f, vari[4]);
                    vari[5] = rtengine::max(0.000001f, vari[5]);
                    vari[6] = rtengine::max(0.000001f, vari[6]);

                    float* noisevarlum = new float[GH * GW];
                    float* noisevarhue = new float[GH * GW];
                    int GW2 = (GW + 1) / 2;

                    float nvlh[13] = {1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.7f, 0.5f}; //high value
                    float nvll[13] = {0.1f, 0.15f, 0.2f, 0.25f, 0.3f, 0.35f, 0.4f, 0.45f, 0.7f, 0.8f, 1.f, 1.f, 1.f}; //low value

                    float seuillow = 3000.f;//low
                    float seuilhigh = 18000.f;//high
                    int i = 10 - lp.noiselequal;
                    float ac = (nvlh[i] - nvll[i]) / (seuillow - seuilhigh);
                    float bc = nvlh[i] - seuillow * ac;
                    //ac and bc for transition
#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int ir = 0; ir < GH; ir++)
                        for (int jr = 0; jr < GW; jr++) {
                            float lN = tmp1.L[ir][jr];

                            if (lN < seuillow) {
                                noisevarlum[(ir >> 1)*GW2 + (jr >> 1)] =  nvlh[i];
                            } else if (lN < seuilhigh) {
                                noisevarlum[(ir >> 1)*GW2 + (jr >> 1)] = ac * lN + bc;
                            } else {
                                noisevarlum[(ir >> 1)*GW2 + (jr >> 1)] =  nvll[i];
                            }
                        }

                    if (lp.enablMask && lp.lnoiselow != 1.f && lp.smasktyp != 0) {
                        //this code has been reviewed by Ingo in september 2020 PR5903
                        float higc;
                        float hig = lp.thrhigh;
                        calcdif(hig, higc);
                        float low = lp.thrlow;
                        float lowc;
                        calcdif(low, lowc);

                        if (higc < lowc) {
                            higc = lowc + 0.01f;
                        }

                        float alow = -(lp.lnoiselow - 1.f) / lowc;
                        float blow = lp.lnoiselow;
                        float ahigh = 0.9999f / (higc - 100.f);
                        float bhigh = 1.f - higc * ahigh;

#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int ir = 0; ir < GH; ir++)
                            for (int jr = 0; jr < GW; jr++) {
                                const float lM = bufmaskblurbl->L[ir][jr];
                                const float lmr = lM / 327.68f;

                                if (lM < 327.68f * lowc) {
                                    noisevarlum[(ir >> 1) * GW2 + (jr >> 1)] *= alow * lmr + blow;
                                } else if (lM < 327.68f * higc) {
                                    // do nothing - denoise not change
                                } else {
                                    noisevarlum[(ir >> 1) * GW2 + (jr >> 1)] *= ahigh * lmr + bhigh;
                                }
                            }
                    }


                    if (HHhuecurve) {
                        //same code as in wavelet levels

#ifdef _OPENMP
                        #pragma omp parallel for
#endif

                        for (int ir = 0; ir < GH; ir++)
                            for (int jr = 0; jr < GW; jr++) {
                                float hueG = xatan2f(tmp1.b[ir][jr], tmp1.a[ir][jr]);
                                float valparam = 2.f * (locwavCurvehue[500.f * static_cast<float>(Color::huelab_to_huehsv2(hueG))] - 0.5f);  //get H=f(H)
                                noisevarhue[(ir >> 1)*GW2 + (jr >> 1)] = 1.f +  valparam;
                                noisevarlum[(ir >> 1)*GW2 + (jr >> 1)] *= noisevarhue[(ir >> 1) * GW2 + (jr >> 1)];
                            }
                    }


                    if ((lp.quamet == 0  && aut == 0) || (mxsl < 1.f && (aut == 1 || aut == 2))) {
                        WaveletDenoiseAllL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);

                    } else if (lp.quamet == 1) {

                        WaveletDenoiseAll_BiShrinkL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);

                        WaveletDenoiseAllL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);

                    }

                    delete[] noisevarlum;
                    delete[] noisevarhue;

                }
            }


            float variC[levred];
            float variCb[levred];

            float noisecfr = lp.noisecf;
            float noiseccr = lp.noisecc;

            if (lp.adjch > 0.f) {
                noisecfr = lp.noisecf + 0.1f * lp.adjch;
                noiseccr = lp.noisecc + 0.1f * lp.adjch;
            }

            float noisecfb = lp.noisecf;
            float noiseccb = lp.noisecc;

            if (lp.adjch < 0.f) {
                noisecfb = lp.noisecf - 0.1f * lp.adjch;
                noiseccb = lp.noisecc - 0.1f * lp.adjch;
            }


            if (noisecfr < 0.f) {
                noisecfr = 0.00001f;
            }

            if (noiseccr < 0.f) {
                noiseccr = 0.00001f;
            }

            if (noisecfb < 0.f) {
                noisecfb = 0.00001f;
            }

            if (noiseccb < 0.f) {
                noiseccb = 0.00001f;
            }

            if (!adecomp.memory_allocation_failed() && !bdecomp.memory_allocation_failed()) {
                float maxccoarse = 0.f;

            edge = 2;
            variC[0] = SQR(noisecfr);
            variC[1] = SQR(noisecfr);
            variC[2] = SQR(noisecfr);
            variC[3] = SQR(1.2f * noisecfr);
            variC[4] = SQR(noisecfr);
            variC[5] = SQR(1.2f * noiseccr);
            variC[6] = SQR(1.5f * noiseccr);

            variCb[0] = SQR(noisecfb);
            variCb[1] = SQR(noisecfb);
            variCb[2] = SQR(noisecfb);
            variCb[3] = SQR(noisecfb);
            variCb[4] = SQR(noisecfb);
            variCb[5] = SQR(1.2f * noiseccb);
            variCb[6] = SQR(1.5f * noiseccb);


                {
                    float minic = 0.000001f;

                    if (noiscfactiv) {
                        minic = 0.1f;//only for artifact shape detection
                    }

                    float k1 = 0.f;
                    float k2 = 0.f;
                    float k3 = 0.f;

                        if (lp.noisecf < 0.2f) {
                            k1 = 0.05f;
                            k2 = 0.f;
                            k3 = 0.f;
                        } else if (lp.noisecf < 0.3f) {
                            k1 = 0.1f;
                            k2 = 0.0f;
                            k3 = 0.f;
                        } else if (lp.noisecf < 0.5f) {
                            k1 = 0.2f;
                            k2 = 0.1f;
                            k3 = 0.f;
                        } else if (lp.noisecf < 0.8f) {
                            k1 = 0.3f;
                            k2 = 0.25f;
                            k3 = 0.f;
                        } else if (lp.noisecf < 1.f) {
                            k1 = 0.4f;
                            k2 = 0.25f;
                            k3 = 0.1f;
                        } else if (lp.noisecf < 2.f) {
                            k1 = 0.5f;
                            k2 = 0.3f;
                            k3 = 0.15f;
                        } else if (lp.noisecf < 3.f) {
                            k1 = 0.6f;
                            k2 = 0.45f;
                            k3 = 0.3f;
                        } else if (lp.noisecf < 4.f) {
                            k1 = 0.7f;
                            k2 = 0.5f;
                            k3 = 0.4f;
                        } else if (lp.noisecf < 5.f) {
                            k1 = 0.8f;
                            k2 = 0.6f;
                            k3 = 0.5f;
                        } else if (lp.noisecf < 6.f) {
                            k1 = 0.85f;
                            k2 = 0.7f;
                            k3 = 0.6f;
                        } else if (lp.noisecf < 8.f) {
                            k1 = 0.9f;
                            k2 = 0.8f;
                            k3 = 0.7f;
                        } else if (lp.noisecf < 10.f) {
                            k1 = 1.f;
                            k2 = 1.f;
                            k3 = 0.9f;

                        } else {
                            k1 = 1.f;
                            k2 = 1.f;
                            k3 = 1.5f;
                        }

                    variC[0] = rtengine::max(minic, variC[0]);
                    variC[1] = rtengine::max(minic, k1 * variC[1]);
                    variC[2] = rtengine::max(minic, k2 * variC[2]);
                    variC[3] = rtengine::max(minic, k3 * variC[3]);

                    variCb[0] = rtengine::max(minic, variCb[0]);
                    variCb[1] = rtengine::max(minic, k1 * variCb[1]);
                    variCb[2] = rtengine::max(minic, k2 * variCb[2]);
                    variCb[3] = rtengine::max(minic, k3 * variCb[3]);

                        float k4 = 0.f;
                        float k5 = 0.f;
                        float k6 = 0.f;

                        if (lp.noisecc < 0.2f) {
                            k4 = 0.1f;
                            k5 = 0.02f;
                        } else if (lp.noisecc < 0.5f) {
                            k4 = 0.15f;
                            k5 = 0.05f;
                        } else if (lp.noisecc < 1.f) {
                            k4 = 0.15f;
                            k5 = 0.1f;
                        } else if (lp.noisecc < 3.f) {
                            k4 = 0.3f;
                            k5 = 0.15f;
                        } else if (lp.noisecc < 4.f) {
                            k4 = 0.6f;
                            k5 = 0.4f;
                        } else if (lp.noisecc < 6.f) {
                            k4 = 0.8f;
                            k5 = 0.6f;
                        } else {
                            k4 = 1.f;
                            k5 = 1.f;
                        }

                        variC[4] = rtengine::max(0.000001f, k4 * variC[4]);
                        variC[5] = rtengine::max(0.000001f, k5 * variC[5]);
                        variCb[4] = rtengine::max(0.000001f, k4 * variCb[4]);
                        variCb[5] = rtengine::max(0.000001f, k5 * variCb[5]);

                        if (lp.noisecc < 4.f) {
                            k6 = 0.f;
                        } else if (lp.noisecc < 5.f) {
                            k6 = 0.4f;
                        } else if (lp.noisecc < 6.f) {
                            k6 = 0.7f;
                        } else {
                            k6 = 1.f;
                        }

                        variC[6] = rtengine::max(0.00001f, k6 * variC[6]);
                        variCb[6] = rtengine::max(0.00001f, k6 * variCb[6]);


                    float* noisevarchrom = new float[GH * GW];
                    //noisevarchrom in function chroma
                    int GW2 = (GW + 1) / 2;
                    float nvch = 0.6f;//high value
                    float nvcl = 0.1f;//low value

                    if (lp.noisecf > 100.f) {
                        nvch = 0.8f;
                        nvcl = 0.4f;
                    }

                    float seuil = 4000.f;//low
                    float seuil2 = 15000.f;//high
                    //ac and bc for transition
                    float ac = (nvch - nvcl) / (seuil - seuil2);
                    float bc = nvch - seuil * ac;
#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int ir = 0; ir < GH; ir++)
                        for (int jr = 0; jr < GW; jr++) {
                            float cN = std::sqrt(SQR(tmp1.a[ir][jr]) + SQR(tmp1.b[ir][jr]));

                            if (cN < seuil) {
                                noisevarchrom[(ir >> 1)*GW2 + (jr >> 1)] =  nvch;
                            } else if (cN < seuil2) {
                                noisevarchrom[(ir >> 1)*GW2 + (jr >> 1)] = ac * cN + bc;
                            } else {
                                noisevarchrom[(ir >> 1)*GW2 + (jr >> 1)] =  nvcl;
                            }
                        }


                    float noisevarab_r = 100.f; //SQR(lp.noisecc / 10.0);

                    if ((lp.quamet == 0  && aut == 0) || (maxccoarse < 0.1f && (aut == 1 || aut == 2)))  {
                        WaveletDenoiseAllAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);
                        WaveletDenoiseAllAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                    } else if (lp.quamet == 1) {
                        WaveletDenoiseAll_BiShrinkAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);
                        WaveletDenoiseAllAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);

                        WaveletDenoiseAll_BiShrinkAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                        WaveletDenoiseAllAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                    }

                    delete[] noisevarchrom;

                }
            }

            if (!Ldecomp.memory_allocation_failed()) {
                Lin = new array2D<float>(GW, GH);
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int i = 0; i < GH; ++i) {
                    for (int j = 0; j < GW; ++j) {
                        (*Lin)[i][j] = tmp1.L[i][j];
                    }
                }

                Ldecomp.reconstruct(tmp1.L[0]);
            }

            if (!Ldecomp.memory_allocation_failed() && aut == 0) {
                if ((lp.noiself >= 0.01f ||  lp.noiself0 >= 0.01f ||  lp.noiself2 >= 0.01f || lp.wavcurvedenoi || lp.noiselc >= 0.01f) && levred == 7 && lp.noiseldetail != 100.f  && lp.quamet < 2) {
                    fftw_denoise(sk, GW, GH, max_numblox_W, min_numblox_W, tmp1.L, Lin,  numThreads, lp, 0);
                }
            }

            if (!adecomp.memory_allocation_failed()) {
                Ain = new array2D<float>(GW, GH);
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int i = 0; i < GH; ++i) {
                    for (int j = 0; j < GW; ++j) {
                        (*Ain)[i][j] = tmp1.a[i][j];
                    }
                }

                adecomp.reconstruct(tmp1.a[0]);
            }


            if (!adecomp.memory_allocation_failed() && aut == 0) {
                if ((lp.noisecf >= 0.01f ||  lp.noisecc >= 0.01f) && levred == 7 && lp.noisechrodetail != 100.f) {
                    fftw_denoise(sk, GW, GH, max_numblox_W, min_numblox_W, tmp1.a, Ain,  numThreads, lp, 1);
                }
            }


            if (!bdecomp.memory_allocation_failed()) {

                Bin = new array2D<float>(GW, GH);
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int i = 0; i < GH; ++i) {
                    for (int j = 0; j < GW; ++j) {
                        (*Bin)[i][j] = tmp1.b[i][j];
                    }
                }

                bdecomp.reconstruct(tmp1.b[0]);
            }


            if (!bdecomp.memory_allocation_failed() && aut == 0) {
                if ((lp.noisecf >= 0.01f ||  lp.noisecc >= 0.01f) && levred == 7 && lp.noisechrodetail != 100.f) {
                    fftw_denoise(sk, GW, GH, max_numblox_W, min_numblox_W, tmp1.b, Bin,  numThreads, lp, 1);
                }

            }
            if(gamma > 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < GH; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < GW - 3; x += 4) {
                        STVFU(tmp1.L[y][x], F2V(32768.f) * gammalog(LVFU(tmp1.L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[3]), F2V(g_a[4])));
                    }

#endif

                    for (; x < GW; ++x) {
                        tmp1.L[y][x] = 32768.f * gammalog(tmp1.L[y][x] / 32768.f, gamma, ts, g_a[3], g_a[4]);
                    }
                }
            }

            if(lp.smasktyp != 0) {
                if(lp.enablMask && lp.recothrd != 1.f) {
                    LabImage tmp3(GW, GH);

                    for (int ir = 0; ir < GH; ir++) {
                        for (int jr = 0; jr < GW; jr++) {
                            tmp3.L[ir][jr] = original->L[ir][jr];
                            tmp3.a[ir][jr] = original->a[ir][jr];
                            tmp3.b[ir][jr] = original->b[ir][jr];
                        }
                    }

                    array2D<float> masklum(GW, GH);
                    array2D<float> masklumch(GW, GH);

                    float hig = lp.higthrd;
                    float higc;
                    calcdif(hig, higc);
                    float low = lp.lowthrd;
                    float lowc;
                    calcdif(low, lowc);
                    float mid = 0.01f * lp.midthrd;
                    float midch = 0.01f * lp.midthrdch;

                    if (higc < lowc) {
                        higc = lowc + 0.01f;
                    }

                    float th = (lp.recothrd - 1.f);
                    float ahigh = th / (higc - 100.f);
                    float bhigh = 1.f - higc * ahigh;

                    float alow = th / lowc;
                    float blow = 1.f - th;
#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int ir = 0; ir < GH; ir++) {
                        for (int jr = 0; jr < GW; jr++) {
                            const float lmr = bufmaskblurbl->L[ir][jr] / 327.68f;
                            float k;
                            float kch;

                            if (lmr < lowc) {
                                k = alow * lmr + blow;
                                kch = alow * lmr + blow;
                            } else if (lmr < higc) {
                                k = 1.f - mid;
                                kch = 1.f - midch;
                            } else {
                                k = ahigh * lmr + bhigh;
                                kch = ahigh * lmr + bhigh;
                            }

                            if (lp.invmaskd) {
                                masklum[ir][jr] = 1.f - pow_F(k, lp.decayd);
                                masklumch[ir][jr] = 1.f - pow_F(kch, lp.decayd);
                            } else {
                                masklum[ir][jr] = pow_F(k, lp.decayd);
                                masklumch[ir][jr] = pow_F(kch, lp.decayd);
                            }
                        }
                    }

                    for (int i = 0; i < 3; ++i) {
                        boxblur(static_cast<float**>(masklum), static_cast<float**>(masklum), 10 / sk, GW, GH, multiThread);
                        boxblur(static_cast<float**>(masklumch), static_cast<float**>(masklumch), 10 / sk, GW, GH, multiThread);
                    }

#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int i = 0; i < GH; ++i) {
                        for (int j = 0; j < GW; ++j) {
                            tmp1.L[i][j] = (tmp3.L[i][j] - tmp1.L[i][j]) *  LIM01(masklum[i][j]) + tmp1.L[i][j];
                            tmp1.a[i][j] = (tmp3.a[i][j] - tmp1.a[i][j]) *  LIM01(masklumch[i][j]) + tmp1.a[i][j];
                            tmp1.b[i][j] = (tmp3.b[i][j] - tmp1.b[i][j]) *  LIM01(masklumch[i][j]) + tmp1.b[i][j];
                        }
                    }

                    masklum.free();
                    masklumch.free();
                }
                
// re read wavelet decomposition to calaculate noise residual
            float chresid = 0.f;
            float chresidtemp = 0.f;
            float chmaxresid = 0.f;
            float chmaxresidtemp = 0.f;
            float chresid46 = 0.f;
            float chresidtemp46 = 0.f;
            float chmaxresid46 = 0.f;
            float chmaxresidtemp46 = 0.f;
            float Lresid = 0.f;
            float Lmaxresid = 0.f;
            float Lresid46 = 0.f;
            float Lmaxresid46 = 0.f;
            
            
//calculate and display residual noise luma and chroma
// various coefficient from  1 to 5 - tries to take into account the difference between calculate Noise and percepted noise
            wavelet_decomposition Ldecompinf(tmp1.L[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);
            wavelet_decomposition adecompinf(tmp1.a[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);
            wavelet_decomposition bdecompinf(tmp1.b[0], tmp1.W, tmp1.H, levwavL, 1, skip, numThreads, lp.daubLen);

            Noise_residualAB(adecompinf, chresid, chmaxresid, false, 0, 3);
            chresidtemp = chresid;
            chmaxresidtemp = chmaxresid;
            Noise_residualAB(bdecompinf, chresid, chmaxresid, false, 0, 3);
            chresid += chresidtemp;
            chmaxresid += chmaxresidtemp;
            int nbmaddir = 4;
            chresid = sqrt(chresid / ( 3 * nbmaddir * 2));
            resi[0] = chresid + 0.5f * (sqrt(chmaxresid) - chresid); //evaluate sigma
            resi[1] = chresid;
            resi[0] /= 1.4f;//arbitrary coefficient
            resi[1] /= 1.4f;



            Noise_residualAB(adecompinf, chresid46, chmaxresid46, false, 4, 6);
            nbmaddir = 3;
            chresidtemp46 = chresid46;
            chmaxresidtemp46 = chmaxresid46;
            Noise_residualAB(bdecompinf, chresid46, chmaxresid46, false, 4, 6);
            chresid46 += chresidtemp46;
            chmaxresid46 += chmaxresidtemp46;
            chresid46 = sqrt(chresid46 / ( 3 * nbmaddir * 2));
            resi[2] = chresid46 + 0.5f * (sqrt(chmaxresid46) - chresid46); //evaluate sigma
            resi[3] = chresid46;
            resi[2] /= 2.f;//arbitrary coefficient
            resi[3] /= 2.f;
            


            Noise_residualAB(Ldecompinf, Lresid, Lmaxresid, false, 0, 3);
            nbmaddir = 4;
            Lresid = sqrt(Lresid / (3 * nbmaddir));
            resi[5] = Lresid + 0.5f * (sqrt(Lmaxresid) - Lresid); //evaluate sigma
            resi[4] = Lresid;
            resi[4] /= 2.f;//arbitrary coefficient
            resi[5] /= 2.f;

            Noise_residualAB(Ldecompinf, Lresid46, Lmaxresid46, false, 4, 6);
            nbmaddir = 3;
            Lresid46 = sqrt(Lresid46 / (3 * nbmaddir));
            resi[6] = Lresid46 + 0.5f * (sqrt(Lmaxresid46) - Lresid46); //evaluate sigma
            resi[7] = Lresid46;
            resi[6] /= 5.f;//arbitrary coefficient
            resi[7] /= 5.f;

//begin denoise with contrast threshold
            bool autode = lp.denoAutocontr;
            float denoco = denocont;
            if(!autode) {
                denoco = lp.denocontra;
            }

            if(lp.enacontr){        
                bool contshow = lp.contrsho;
                TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);

                const float wip[3][3] = {
                    {(float) wprof[0][0], (float) wprof[0][1], (float) wprof[0][2]},
                    {(float) wprof[1][0], (float) wprof[1][1], (float) wprof[1][2]},
                    {(float) wprof[2][0], (float) wprof[2][1], (float) wprof[2][2]}
                };

                const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(original->W, original->H));//all image
                LabImage tmpori(transformed->W, transformed->H);
                for (int ir = 0; ir < GH; ir++)
                    for (int jr = 0; jr < GW; jr++) {
                        tmpori.L[ir][jr] = original->L[ir][jr];
                        tmpori.a[ir][jr] = original->a[ir][jr];
                        tmpori.b[ir][jr] = original->b[ir][jr];
                    }
                        if (HHhuecurvecont) {
#ifdef _OPENMP
                        #pragma omp parallel for
#endif

                            for (int ir = 0; ir < GH; ir++)
                                for (int jr = 0; jr < GW; jr++) {
                                    float hueG = xatan2f(tmpori.b[ir][jr], tmpori.a[ir][jr]);
                                    float chroG = std::sqrt(SQR(tmpori.b[ir][jr]) + SQR(tmpori.a[ir][jr]));
                                    float valparam = 2.f * (locwavCurvehuecont[500.f * static_cast<float>(Color::huelab_to_huehsv2(hueG))] - 0.5f);  //get H=f(H)
                                    float2 sincosval = xsincosf(valparam);
                                    tmpori.L[ir][jr] *=  1.f +  valparam;  //increase L 
                                    tmpori.a[ir][jr] = chroG * sincosval.y * (1.f -  abs(valparam)); // reduce impact noise chroma 
                                    tmpori.b[ir][jr] = chroG * sincosval.x * (1.f -  abs(valparam)); // reduce impact noise chroma
                               
                                }
                        }
                
                
                
                lab2rgb(tmpori, *tmpImage, params->icm.workingProfile);//copy original  image lab to RGB

                array2D<float> clipMask(GW, GH);       
                array2D<float> clipMaskchro(GW, GH);       
                     
                array2D<float> redVals (GW, GH);
                array2D<float> greenVals(GW, GH);
                array2D<float> blueVals(GW, GH);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                for (int i = 0; i < GH; ++i) {
                    for (int j = 0; j < GW; ++j) {
                        redVals[i][j] = tmpImage->r(i,j);
                        greenVals[i][j] = tmpImage->g(i,j);
                        blueVals[i][j] = tmpImage->b(i,j);
                    }
                }
                float denstr = lp.denomas;
               
                if(lp.denomas > 0.f) {//denoise mask
                //here I use only median (faster), because I act only on mask and not on image.
                    array2D<float> tmL (GW, GH);
                    array2D<float> mR (GW, GH);
                    array2D<float> mG (GW, GH);
                    array2D<float> mB (GW, GH);
                    
                    
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic, 16)
#endif                  
                    for (int i = 0; i < GH; ++i) {
                        for (int j = 0; j < GW; ++j) {
                            mR[i][j] = redVals[i][j];
                            mG[i][j] = greenVals[i][j];
                            mB[i][j] = blueVals[i][j];
                        }
                    }
                    ImProcFunctions::Median medianTypeL = Median::TYPE_3X3_SOFT;

                    int itera = 1;
                    if(denstr < 0.1f) {
                        medianTypeL = Median::TYPE_3X3_SOFT;
                        itera = 1;
                    } else if (denstr < 0.2f) {
                        medianTypeL = Median::TYPE_3X3_SOFT;
                        itera = 2;
                    } else if (denstr < 0.3f) {
                        medianTypeL = Median::TYPE_3X3_SOFT;
                        itera = 3;
                    } else if (denstr < 0.4f) {
                        medianTypeL = Median::TYPE_3X3_STRONG;
                        itera = 3;
                    } else if (denstr < 0.5f) {
                        medianTypeL = Median::TYPE_3X3_STRONG;
                        itera = 4;
                    } else if (denstr < 0.6f) {
                        medianTypeL = Median::TYPE_5X5_STRONG;
                        itera = 5;
                    } else if (denstr < 0.7f) {
                        medianTypeL = Median::TYPE_5X5_STRONG;
                        itera = 6;
                    } else if (denstr < 0.8f) {//slow
                        medianTypeL = Median::TYPE_7X7;
                        itera = 3;
                    } else if (denstr < 0.9f) {
                        medianTypeL = Median::TYPE_7X7;
                        itera = 4;
                    } else {//very slow
                        medianTypeL = Median::TYPE_9X9;
                        itera = 3;            
                    }
                    
                    ImProcFunctions::Median_Denoise(mR, mR, GW, GH, medianTypeL , itera, false, tmL);
                    ImProcFunctions::Median_Denoise(mG, mG, GW, GH, medianTypeL , itera, false, tmL);
                    ImProcFunctions::Median_Denoise(mB, mB, GW, GH, medianTypeL , itera, false, tmL);
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic, 16)
#endif
                    for (int i = 0; i < GH; ++i) {
                        for (int j = 0; j < GW; ++j) {
                            redVals[i][j] = intp(denstr, mR[i][j], redVals[i][j]); 
                            greenVals[i][j] = intp(denstr, mG[i][j], greenVals[i][j]); 
                            blueVals[i][j] = intp(denstr, mB[i][j], blueVals[i][j]); 
                        }
                    }

                }

                float s_scale = std::sqrt(sk);
                float contrast = pow_F(denoco / 100.f, 1.f) * s_scale;
                  
                array2D<float> Y (GW, GH);
        
                for (int i = 0; i < GH; ++i) {
                    Color::RGB2L(redVals[i], greenVals[i], blueVals[i], Y[i], wip, GW);
                }
                float reducautocontrast = 1.f;//to take noise into account
                buildBlendMask2(Y, clipMask, GW, GH, contrast, 1.f, autode, 2.f / s_scale, 1.f, reducautocontrast);
               
                const std::unique_ptr<LabImage> copyorig(new LabImage(original->W, original->H));//copy original image to keep initial datas
                

                denocont = 100.f * pow_F(contrast, 1.f)/ s_scale;
                
                if(contshow) {              
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                    for (int i = 0; i < GH; ++i) {
                        for (int j = 0; j < GW; ++j) {
                            tmpImage->r(i, j)= tmpImage->g(i, j)= tmpImage->b(i, j) =  clipMask[i][j] * 65536.f;              
                        }
                    }
                
                    rgb2lab(*tmpImage, *copyorig, params->icm.workingProfile);//conver all image lo Lab

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < GH; ir++) {
                        for (int jr = 0; jr < GW; jr++) {
                            tmp1.L[ir][jr] = copyorig->L[ir][jr];
                            tmp1.a[ir][jr] = copyorig->a[ir][jr];
                            tmp1.b[ir][jr] = copyorig->b[ir][jr];
                        }
                    }
                } else {
               
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                    for (int ir = 0; ir < GH; ir++) {
                        for (int jr = 0; jr < GW; jr++) {
                            tmp1.L[ir][jr] = intp(lp.denorati * clipMask[ir][jr], tmp4.L[ir][jr], tmp1.L[ir][jr]);
                            tmp1.a[ir][jr] = intp(lp.denorati * clipMask[ir][jr], tmp4.a[ir][jr], tmp1.a[ir][jr]);
                            tmp1.b[ir][jr] = intp(lp.denorati * clipMask[ir][jr], tmp4.b[ir][jr], tmp1.b[ir][jr]);
                        }
                    } 
                }          
            }
            //end denoise with contrast threshold mask
// end calculate
                
            DeNoise_Local(call, lp,  originalmaskbl, levred, huerefblur, lumarefblur, chromarefblur, original, transformed, tmp1, cx, cy, sk);

            } else {
                DeNoise_Local(call, lp,  original, levred, huerefblur, lumarefblur, chromarefblur, original, transformed, tmp1, cx, cy, sk);
            }
        } else if (call == 2) { //simpleprocess

            const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
            const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
            const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
            const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
            const int bfh = yend - ystart;
            const int bfw = xend - xstart;

            if (bfh >= mDEN && bfw >= mDEN) {
                LabImage bufwv(bfw, bfh);
                LabImage bufwv4(bfw, bfh);
                bufwv.clear(true);
                bufwv4.clear(true);
                
                array2D<float> *Lin = nullptr;
                array2D<float> *Ain = nullptr;
                array2D<float> *Bin = nullptr;

                int max_numblox_W = ceil((static_cast<float>(bfw)) / offset) + 2;
                // calculate min size of numblox_W.
                int min_numblox_W = ceil((static_cast<float>(bfw)) / offset) + 2;
                // these are needed only for creation of the plans and will be freed before entering the parallel loop


                int begy = ystart; //lp.yc - lp.lyT;
                int begx = xstart; //lp.xc - lp.lxL;
                int yEn = yend; //lp.yc + lp.ly;
                int xEn = xend; //lp.xc + lp.lx;

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < transformed->H ; y++) //{
                    for (int x = 0; x < transformed->W; x++) {
                        int lox = cx + x;
                        int loy = cy + y;

                        if (lox >= begx && lox < xEn && loy >= begy && loy < yEn) {
                            bufwv.L[loy - begy][lox - begx] = original->L[y][x];
                            bufwv.a[loy - begy][lox - begx] = original->a[y][x];
                            bufwv.b[loy - begy][lox - begx] = original->b[y][x];
                            bufwv4.L[loy - begy][lox - begx] = original->L[y][x];
                            bufwv4.a[loy - begy][lox - begx] = original->a[y][x];
                            bufwv4.b[loy - begy][lox - begx] = original->b[y][x];
                        }

                    }

                float gamma = lp.noisegam;
                rtengine::GammaValues g_a; //gamma parameters
                double pwr = 1.0 / (double) lp.noisegam;//default 3.0 - gamma Lab
                double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

                if (gamma > 1.f) {
#ifdef _OPENMP
                    #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bfh; ++y) {
                        int x = 0;

#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x <  bfw - 3; x += 4) {
                            STVFU(bufwv.L[y][x], F2V(32768.f) * igammalog(LVFU(bufwv.L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[2]), F2V(g_a[4])));
                        }

#endif

                        for (; x < bfw; ++x) {
                            bufwv.L[y][x] = 32768.f * igammalog(bufwv.L[y][x] / 32768.f, gamma, ts, g_a[2], g_a[4]);
                        }
                    }
                }


                int levwavL = levred;
                int skip = 1;
                wavelet_decomposition Ldecomp(bufwv.L[0], bufwv.W, bufwv.H, levwavL, 1, skip, numThreads, lp.daubLen);
                wavelet_decomposition adecomp(bufwv.a[0], bufwv.W, bufwv.H, levwavL, 1, skip, numThreads, lp.daubLen);
                wavelet_decomposition bdecomp(bufwv.b[0], bufwv.W, bufwv.H, levwavL, 1, skip, numThreads, lp.daubLen);
                float madL[10][3];
                int edge = 2;

                if (!Ldecomp.memory_allocation_failed()) {
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic) collapse(2) if (multiThread)
#endif

                    for (int lvl = 0; lvl < levred; lvl++) {
                        for (int dir = 1; dir < 4; dir++) {
                            int Wlvl_L = Ldecomp.level_W(lvl);
                            int Hlvl_L = Ldecomp.level_H(lvl);

                            const float* const* WavCoeffs_L = Ldecomp.level_coeffs(lvl);

                            madL[lvl][dir - 1] = SQR(Mad(WavCoeffs_L[dir], Wlvl_L * Hlvl_L));
                        }
                    }
                    if (params->locallab.spots.at(sp).lockmadl) {
                        for (int lvl = 0; lvl < levred; lvl++) {
                            for (int dir = 1; dir < 4; dir++) {
                                madL[lvl][dir - 1] = params->locallab.spots.at(sp).madlsav[lvl +  (dir - 1) * 7];
                            }
                        }  
                    }
                    
                    if (settings->verbose) {
                        for (int lvl = 0; lvl < levred; lvl++) {
                            for (int dir = 1; dir < 4; dir++) {
                                 printf("Output level=%i dir=%i madL=%6.0f\n", lvl, dir-1, (double) madL[lvl][dir-1]);                               
                            }
                        }
                    }

                    float vari[levred];
                    float mxsl = 0.f;

                        {
                            edge = 2;
                            vari[0] = 0.8f * SQR((lp.noiself0 / 125.f) * (1.f + lp.noiself0 / 25.f));
                            vari[1] = 0.8f * SQR((lp.noiself / 125.f) * (1.f + lp.noiself / 25.f));
                            vari[2] = 0.8f * SQR((lp.noiself2 / 125.f) * (1.f + lp.noiself2 / 25.f));
                            vari[3] = 0.8f * SQR((lp.noiselc / 125.f) * (1.f + lp.noiselc / 25.f));
                            vari[4] = 1.f * SQR((lp.noiselc4 / 125.f) * (1.f + lp.noiselc4 / 25.f));
                            vari[5] = 1.5f * SQR((lp.noiselc5 / 125.f) * (1.f + lp.noiselc5 / 25.f));
                            vari[6] = 2.5f * SQR((lp.noiselc6 / 125.f) * (1.f + lp.noiselc6 / 25.f));
                        } 

                    {
                        float kr3 = 0.f;

                        {
                            if (lp.noiselc < 30.f) {
                                kr3 = 0.f;
                            } else if (lp.noiselc < 50.f) {
                                kr3 = 0.5f;
                            } else if (lp.noiselc < 70.f) {
                                kr3 = 0.7f;
                            } else {
                                kr3 = 1.f;
                            }
                        } 

                        vari[0] = rtengine::max(0.000001f, vari[0]);
                        vari[1] = rtengine::max(0.000001f, vari[1]);
                        vari[2] = rtengine::max(0.000001f, vari[2]);
                        vari[3] = rtengine::max(0.000001f, kr3 * vari[3]);
                        vari[4] = rtengine::max(0.000001f, vari[4]);
                        vari[5] = rtengine::max(0.000001f, vari[5]);
                        vari[6] = rtengine::max(0.000001f, vari[6]);

                        //    float* noisevarlum = nullptr;  // we need a dummy to pass it to WaveletDenoiseAllL
                        float* noisevarlum = new float[bfh * bfw];
                        float* noisevarhue = new float[bfh * bfw];
                        int bfw2 = (bfw + 1) / 2;

                        float nvlh[13] = {1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.7f, 0.5f}; //high value
                        float nvll[13] = {0.1f, 0.15f, 0.2f, 0.25f, 0.3f, 0.35f, 0.4f, 0.45f, 0.7f, 0.8f, 1.f, 1.f, 1.f}; //low value

                        float seuillow = 3000.f;//low
                        float seuilhigh = 18000.f;//high
                        int i = 10 - lp.noiselequal;
                        float ac = (nvlh[i] - nvll[i]) / (seuillow - seuilhigh);
                        float bc = nvlh[i] - seuillow * ac;
                        //ac and bc for transition
#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                float lN = bufwv.L[ir][jr];

                                if (lN < seuillow) {
                                    noisevarlum[(ir >> 1)*bfw2 + (jr >> 1)] =  nvlh[i];
                                } else if (lN < seuilhigh) {
                                    noisevarlum[(ir >> 1)*bfw2 + (jr >> 1)] = ac * lN + bc;
                                } else {
                                    noisevarlum[(ir >> 1)*bfw2 + (jr >> 1)] =  nvll[i];
                                }
                            }

                        if (lp.enablMask && lp.lnoiselow != 1.f  && lp.smasktyp != 0) {
                            //this code has been reviewed by Ingo in september 2020 PR5903
                            //i just change parameters to better progressivity
                            float higc;
                            float hig = lp.thrhigh;
                            calcdif(hig, higc);
                            float low = lp.thrlow;
                            float lowc;
                            calcdif(low, lowc);

                            if (higc < lowc) {
                                higc = lowc + 0.01f;
                            }

                            float alow = -(lp.lnoiselow - 1.f) / lowc;
                            float blow = lp.lnoiselow;
                            float ahigh = 0.9999f / (higc - 100.f);
                            float bhigh = 1.f - higc * ahigh;


#ifdef _OPENMP
                            #pragma omp parallel for if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float lM = bufmaskblurbl->L[ir + ystart][jr + xstart];
                                    const float lmr = lM / 327.68f;

                                    if (lM < 327.68f * lowc) {
                                        noisevarlum[(ir >> 1) * bfw2 + (jr >> 1)] *= alow * lmr + blow;
                                    } else if (lM < 327.68f * higc) {
                                        // do nothing
                                    } else {
                                        noisevarlum[(ir >> 1) * bfw2 + (jr >> 1)] *= ahigh * lmr + bhigh;
                                    }
                                }
                        }


                        if (HHhuecurve) {
                            //same code as in wavelet levels
#ifdef _OPENMP
                            #pragma omp parallel for
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    float hueG = xatan2f(bufwv.b[ir][jr], bufwv.a[ir][jr]);
                                    float valparam = 2.f * (locwavCurvehue[500.f * static_cast<float>(Color::huelab_to_huehsv2(hueG))] - 0.5f);  //get H=f(H)
                                    noisevarhue[(ir >> 1)* bfw2 + (jr >> 1)] = 1.f +  valparam;
                                    noisevarlum[(ir >> 1)* bfw2 + (jr >> 1)] *= noisevarhue[(ir >> 1) * bfw2 + (jr >> 1)];
                                }
                        }


                        if ((lp.quamet == 0  && aut == 0) || (mxsl < 1.f && (aut == 1 || aut == 2))) {
                            WaveletDenoiseAllL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);
                        } else if (lp.quamet == 1) {
                            WaveletDenoiseAll_BiShrinkL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);
                            WaveletDenoiseAllL(Ldecomp, noisevarlum, madL, vari, edge, numThreads);
                        }

                        delete [] noisevarlum;
                        delete [] noisevarhue;

                    }
                }


                float variC[levred];
                float variCb[levred];

                float noisecfr = lp.noisecf;
                float noiseccr = lp.noisecc;

                if (lp.adjch > 0.f) {
                    noisecfr = lp.noisecf + 0.1f * lp.adjch;
                    noiseccr = lp.noisecc + 0.1f * lp.adjch;
                }

                float noisecfb = lp.noisecf;
                float noiseccb = lp.noisecc;

                if (lp.adjch < 0.f) {
                    noisecfb = lp.noisecf - 0.1f * lp.adjch;
                    noiseccb = lp.noisecc - 0.1f * lp.adjch;
                }


                if (noisecfr < 0.f) {
                    noisecfr = 0.00001f;
                }

                if (noiseccr < 0.f) {
                    noiseccr = 0.00001f;
                }

                if (noisecfb < 0.f) {
                    noisecfb = 0.00001f;
                }

                if (noiseccb < 0.f) {
                    noiseccb = 0.00001f;
                }


                if (!adecomp.memory_allocation_failed() && !bdecomp.memory_allocation_failed()) {
                    float maxccoarse = 0.f;


                        {
                            edge = 2;
                            variC[0] = SQR(noisecfr);
                            variC[1] = SQR(noisecfr);
                            variC[2] = SQR(noisecfr);
                            variC[3] = SQR(1.2f * noisecfr);
                            variC[4] = SQR(noisecfr);
                            variC[5] = SQR(1.2f * noiseccr);
                            variC[6] = SQR(1.5f * noiseccr);

                            variCb[0] = SQR(noisecfb);
                            variCb[1] = SQR(noisecfb);
                            variCb[2] = SQR(noisecfb);
                            variCb[3] = SQR(noisecfb);
                            variCb[4] = SQR(noisecfb);
                            variCb[5] = SQR(1.2f * noiseccb);
                            variCb[6] = SQR(1.5f * noiseccb);

                        } 

                    {
                        float minic = 0.000001f;

                        if (noiscfactiv) {
                            minic = 0.1f;//only for artifact shape detection
                        }

                        float k1 = 0.f;
                        float k2 = 0.f;
                        float k3 = 0.f;

                            if (lp.noisecf < 0.2f) {
                                k1 = 0.05f;
                                k2 = 0.f;
                                k3 = 0.f;
                            } else if (lp.noisecf < 0.3f) {
                                k1 = 0.1f;
                                k2 = 0.0f;
                                k3 = 0.f;
                            } else if (lp.noisecf < 0.5f) {
                                k1 = 0.2f;
                                k2 = 0.1f;
                                k3 = 0.f;
                            } else if (lp.noisecf < 0.8f) {
                                k1 = 0.3f;
                                k2 = 0.25f;
                                k3 = 0.f;
                            } else if (lp.noisecf < 1.f) {
                                k1 = 0.4f;
                                k2 = 0.25f;
                                k3 = 0.1f;
                            } else if (lp.noisecf < 2.f) {
                                k1 = 0.5f;
                                k2 = 0.3f;
                                k3 = 0.15f;
                            } else if (lp.noisecf < 3.f) {
                                k1 = 0.6f;
                                k2 = 0.45f;
                                k3 = 0.3f;
                            } else if (lp.noisecf < 4.f) {
                                k1 = 0.7f;
                                k2 = 0.5f;
                                k3 = 0.4f;
                            } else if (lp.noisecf < 5.f) {
                                k1 = 0.8f;
                                k2 = 0.6f;
                                k3 = 0.5f;
                            } else if (lp.noisecf < 6.f) {
                                k1 = 0.85f;
                                k2 = 0.7f;
                                k3 = 0.6f;
                            } else if (lp.noisecf < 8.f) {
                                k1 = 0.9f;
                                k2 = 0.8f;
                                k3 = 0.7f;
                            } else if (lp.noisecf < 10.f) {
                                k1 = 1.f;
                                k2 = 1.f;
                                k3 = 0.9f;

                            } else {
                                k1 = 1.f;
                                k2 = 1.f;
                                k3 = 1.5f;
                            }

                        variC[0] = rtengine::max(minic, variC[0]);
                        variC[1] = rtengine::max(minic, k1 * variC[1]);
                        variC[2] = rtengine::max(minic, k2 * variC[2]);
                        variC[3] = rtengine::max(minic, k3 * variC[3]);

                        variCb[0] = rtengine::max(minic, variCb[0]);
                        variCb[1] = rtengine::max(minic, k1 * variCb[1]);
                        variCb[2] = rtengine::max(minic, k2 * variCb[2]);
                        variCb[3] = rtengine::max(minic, k3 * variCb[3]);

                        {
                            float k4 = 0.f;
                            float k5 = 0.f;
                            float k6 = 0.f;

                            if (lp.noisecc < 0.2f) {
                                k4 = 0.1f;
                                k5 = 0.02f;
                            } else if (lp.noisecc < 0.5f) {
                                k4 = 0.15f;
                                k5 = 0.05f;
                            } else if (lp.noisecc < 1.f) {
                                k4 = 0.15f;
                                k5 = 0.1f;
                            } else if (lp.noisecc < 3.f) {
                                k4 = 0.3f;
                                k5 = 0.15f;
                            } else if (lp.noisecc < 4.f) {
                                k4 = 0.6f;
                                k5 = 0.4f;
                            } else if (lp.noisecc < 6.f) {
                                k4 = 0.8f;
                                k5 = 0.6f;
                            } else {
                                k4 = 1.f;
                                k5 = 1.f;
                            }


                            variC[4] = rtengine::max(0.000001f, k4 * variC[4]);
                            variC[5] = rtengine::max(0.000001f, k5 * variC[5]);
                            variCb[4] = rtengine::max(0.000001f, k4 * variCb[4]);
                            variCb[5] = rtengine::max(0.000001f, k5 * variCb[5]);

                            if (lp.noisecc < 4.f) {
                                k6 = 0.f;
                            } else if (lp.noisecc < 5.f) {
                                k6 = 0.4f;
                            } else if (lp.noisecc < 6.f) {
                                k6 = 0.7f;
                            } else {
                                k6 = 1.f;
                            }

                            variC[6] = rtengine::max(0.00001f, k6 * variC[6]);
                            variCb[6] = rtengine::max(0.00001f, k6 * variCb[6]);
                        }

                        float* noisevarchrom = new float[bfh * bfw];
                        int bfw2 = (bfw + 1) / 2;
                        float nvch = 0.6f;//high value
                        float nvcl = 0.1f;//low value

                        if (lp.noisecf > 30.f) {
                            nvch = 0.8f;
                            nvcl = 0.4f;
                        }

                        float seuil = 4000.f;//low
                        float seuil2 = 15000.f;//high
                        //ac and bc for transition
                        float ac = (nvch - nvcl) / (seuil - seuil2);
                        float bc = nvch - seuil * ac;
#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                float cN = std::sqrt(SQR(bufwv.a[ir][jr]) + SQR(bufwv.b[ir][jr]));

                                if (cN < seuil) {
                                    noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] = nvch;
                                } else if (cN < seuil2) {
                                    noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] = ac * cN + bc;
                                } else {
                                    noisevarchrom[(ir >> 1)*bfw2 + (jr >> 1)] = nvcl;
                                }
                            }

                        float noisevarab_r = 100.f; //SQR(lp.noisecc / 10.0);

                        if ((lp.quamet == 0  && aut == 0) || (maxccoarse < 0.1f && (aut == 1  || aut == 2)))  {
                            WaveletDenoiseAllAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);
                            WaveletDenoiseAllAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                        } else if (lp.quamet == 1) {
                            WaveletDenoiseAll_BiShrinkAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);
                            WaveletDenoiseAllAB(Ldecomp, adecomp, noisevarchrom, madL, variC, edge, noisevarab_r, true, false, false, numThreads);

                            WaveletDenoiseAll_BiShrinkAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                            WaveletDenoiseAllAB(Ldecomp, bdecomp, noisevarchrom, madL, variCb, edge, noisevarab_r, true, false, false, numThreads);
                        }

                        delete[] noisevarchrom;
                    }
                }

                if (!Ldecomp.memory_allocation_failed()) {
                    Lin = new array2D<float>(bfw, bfh);

#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int i = 0; i < bfh; ++i) {
                        for (int j = 0; j < bfw; ++j) {
                            (*Lin)[i][j] = bufwv.L[i][j];
                        }
                    }

                    Ldecomp.reconstruct(bufwv.L[0]);
                }


                if (!Ldecomp.memory_allocation_failed() && aut == 0) {


                    if ((lp.noiself >= 0.01f ||  lp.noiself0 >= 0.01f ||  lp.noiself2 >= 0.01f || lp.wavcurvedenoi || lp.noiselc >= 0.01f) && levred == 7 && lp.noiseldetail != 100.f && lp.quamet < 2) {
                        fftw_denoise(sk, bfw, bfh, max_numblox_W, min_numblox_W, bufwv.L, Lin,  numThreads, lp, 0);
                    }
                }


                if (!adecomp.memory_allocation_failed()) {
                    Ain = new array2D<float>(bfw, bfh);
#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int i = 0; i < bfh; ++i) {
                        for (int j = 0; j < bfw; ++j) {
                            (*Ain)[i][j] = bufwv.a[i][j];
                        }
                    }

                    adecomp.reconstruct(bufwv.a[0]);
                }

                if (!adecomp.memory_allocation_failed() && aut == 0) {
                    if ((lp.noisecf >= 0.001f ||  lp.noisecc >= 0.001f) && levred == 7 && lp.noisechrodetail != 100.f) {
                        fftw_denoise(sk, bfw, bfh, max_numblox_W, min_numblox_W, bufwv.a, Ain,  numThreads, lp, 1);
                    }
                }


                if (!bdecomp.memory_allocation_failed()) {
                    Bin = new array2D<float>(bfw, bfh);
#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int i = 0; i < bfh; ++i) {
                        for (int j = 0; j < bfw; ++j) {
                            (*Bin)[i][j] = bufwv.b[i][j];
                        }
                    }

                    bdecomp.reconstruct(bufwv.b[0]);
                }

                if (!bdecomp.memory_allocation_failed() && aut == 0) {
                    if ((lp.noisecf >= 0.001f ||  lp.noisecc >= 0.001f) && levred == 7 && lp.noisechrodetail != 100.f) {
                        fftw_denoise(sk, bfw, bfh, max_numblox_W, min_numblox_W, bufwv.b, Bin,  numThreads, lp, 1);
                    }
                }

                if (gamma > 1.f) {
#ifdef _OPENMP
                    #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bfh ; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                        int x = 0;

#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x < bfw  - 3; x += 4) {

                            STVFU(bufwv.L[y][x], F2V(32768.f) * gammalog(LVFU(bufwv.L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[3]), F2V(g_a[4])));
                        }

#endif

                        for (; x < bfw ; ++x) {

                            bufwv.L[y][x] = 32768.f * gammalog(bufwv.L[y][x] / 32768.f, gamma, ts, g_a[3], g_a[4]);
                        }
                    }
                }

                if (lp.nlstr > 0) {
                    int iter = lp.nliter;//iterations Nlmeans
                    for(int it = 0; it < iter; it++) {
                        NLMeans(bufwv.L, lp.nlstr, lp.nldet, lp.nlpat, lp.nlrad , lp.nlgam, bfw, bfh, 1.f, multiThread);
                    }
                }


                if (lp.smasktyp != 0) {
                    if (lp.enablMask && lp.recothrd != 1.f) {
                        LabImage tmp3(bfw, bfh);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < transformed->H ; y++) {
                            for (int x = 0; x < transformed->W; x++) {
                                int lox = cx + x;
                                int loy = cy + y;

                                if (lox >= begx && lox < xEn && loy >= begy && loy < yEn) {
                                    tmp3.L[loy - begy][lox - begx] = original->L[y][x];
                                    tmp3.a[loy - begy][lox - begx] = original->a[y][x];
                                    tmp3.b[loy - begy][lox - begx] = original->b[y][x];
                                }

                            }
                        }

                        array2D<float> masklum;
                        array2D<float> masklumch;
                        masklum(bfw, bfh);
                        masklumch(bfw, bfh);

                        for (int ir = 0; ir < bfh; ir++) {
                            for (int jr = 0; jr < bfw; jr++) {
                                masklum[ir][jr] = 1.f;
                                masklumch[ir][jr] = 1.f;
                            }
                        }

                        float hig = lp.higthrd;
                        float higc;
                        calcdif(hig, higc);
                        float low = lp.lowthrd;
                        float lowc;
                        calcdif(low, lowc);
                        float mid = 0.01f * lp.midthrd;
                        float midch = 0.01f * lp.midthrdch;

                        if (higc < lowc) {
                            higc = lowc + 0.01f;
                        }

                        float th = (lp.recothrd - 1.f);
                        float ahigh = th / (higc - 100.f);
                        float bhigh = 1.f - higc * ahigh;

                        float alow = th / lowc;
                        float blow = 1.f - th;

#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int y = ystart; y < yend; y++) {
                            for (int x = xstart, lox = cx + x; x < xend; x++, lox++) {

                                const float lM = bufmaskblurbl->L[y][x];
                                const float lmr = lM / 327.68f;

                                if (lM < 327.68f * lowc) {
                                    masklum[y - ystart][x - xstart] = alow * lmr + blow;
                                    masklumch[y - ystart][x - xstart] = alow * lmr + blow;
                                } else if (lM < 327.68f * higc) {
                                    masklum[y - ystart][x - xstart] = 1.f - mid;
                                    masklumch[y - ystart][x - xstart] = 1.f - midch;

                                } else {
                                    masklum[y - ystart][x - xstart] = ahigh * lmr + bhigh;
                                    masklumch[y - ystart][x - xstart] = ahigh * lmr + bhigh;
                                }

                                float k = masklum[y - ystart][x - xstart];
                                float kch = masklumch[y - ystart][x - xstart];

                                if (lp.invmaskd == true) {
                                    masklum[y - ystart][x - xstart] = 1.f - pow(k, lp.decayd);
                                    masklumch[y - ystart][x - xstart] = 1.f - pow(kch, lp.decayd);
                                } else {
                                    masklum[y - ystart][x - xstart] = pow(k, lp.decayd);
                                    masklumch[y - ystart][x - xstart] = pow(kch, lp.decayd);
                                }
                            }
                        }

                        for (int i = 0; i < 3; ++i) {
                            boxblur(static_cast<float**>(masklum), static_cast<float**>(masklum), 10 / sk, bfw, bfh, false);
                            boxblur(static_cast<float**>(masklumch), static_cast<float**>(masklumch), 10 / sk, bfw, bfh, false);
                        }

#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int y = 0; y < bfh; y++) {
                            for (int x = 0; x < bfw; x++) {
                                bufwv.L[y][x] = (tmp3.L[y][x] - bufwv.L[y][x]) *  LIM01(masklum[y][x]) + bufwv.L[y][x];
                                bufwv.a[y][x] = (tmp3.a[y][x] - bufwv.a[y][x]) *  LIM01(masklumch[y][x]) + bufwv.a[y][x];
                                bufwv.b[y][x] = (tmp3.b[y][x] - bufwv.b[y][x]) *  LIM01(masklumch[y][x]) + bufwv.b[y][x];
                            }
                        }

                        masklum.free();
                        masklumch.free();
                    }
//begin denoise with contrast threshold
                    bool autode = lp.denoAutocontr;
                    float denoco = denocont;
                    if(!autode) {
                        denoco = lp.denocontra;
                    }

                if(lp.enacontr){        
                        TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);

                        const float wip[3][3] = {
                            {(float) wprof[0][0], (float) wprof[0][1], (float) wprof[0][2]},
                            {(float) wprof[1][0], (float) wprof[1][1], (float) wprof[1][2]},
                            {(float) wprof[2][0], (float) wprof[2][1], (float) wprof[2][2]}
                        };

                        const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));//all image


                        LabImage tmpori(bfw, bfh);//temp image to take into account equalizer color mask

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < transformed->H ; y++) //{
                            for (int x = 0; x < transformed->W; x++) {
                                int lox = cx + x;
                                int loy = cy + y;

                                if (lox >= begx && lox < xEn && loy >= begy && loy < yEn) {
                                    tmpori.L[loy - begy][lox - begx] = original->L[y][x];
                                    tmpori.a[loy - begy][lox - begx] = original->a[y][x];
                                    tmpori.b[loy - begy][lox - begx] = original->b[y][x];
                                }
        
                            }
                        if (HHhuecurvecont) {
#ifdef _OPENMP
                        #pragma omp parallel for
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    float hueG = xatan2f(tmpori.b[ir][jr], tmpori.a[ir][jr]);
                                    float chroG = std::sqrt(SQR(tmpori.b[ir][jr]) + SQR(tmpori.a[ir][jr]));
                                    float valparam = 2.f * (locwavCurvehuecont[500.f * static_cast<float>(Color::huelab_to_huehsv2(hueG))] - 0.5f);  //get H=f(H)
                                    float2 sincosval = xsincosf(valparam);
                                    tmpori.L[ir][jr] *=  1.f +  valparam;  //increase L 
                                    tmpori.a[ir][jr] = chroG * sincosval.y * (1.f -  abs(valparam)); // reduce impact noise chroma 
                                    tmpori.b[ir][jr] = chroG * sincosval.x * (1.f -  abs(valparam)); // reduce impact noise chroma
                               
                                }
                        }

                        lab2rgb(tmpori, *tmpImage, params->icm.workingProfile);//copy original  image lab to RGB

                        array2D<float> clipMask(bfw, bfh);       
                        array2D<float> clipMaskchro(bfw, bfh);       
                     
                        array2D<float> redVals (bfw, bfh);
                        array2D<float> greenVals(bfw, bfh);
                        array2D<float> blueVals(bfw, bfh);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                        for (int i = 0; i < bfh; ++i) {
                            for (int j = 0; j < bfw; ++j) {
                                redVals[i][j] = tmpImage->r(i,j);
                                greenVals[i][j] = tmpImage->g(i,j);
                                blueVals[i][j] = tmpImage->b(i,j);
                            }
                        }
                        
                        float denstr = lp.denomas;
               
                    if(lp.denomas > 0.f) {//denoise mask
                            array2D<float> tmL (bfw, bfh);
                            array2D<float> mR (bfw, bfh);
                            array2D<float> mG (bfw, bfh);
                            array2D<float> mB (bfw, bfh);
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic, 16)
#endif                  
                            for (int i = 0; i < bfh; ++i) {
                                for (int j = 0; j < bfw; ++j) {
                                    mR[i][j] = redVals[i][j];
                                    mG[i][j] = greenVals[i][j];
                                    mB[i][j] = blueVals[i][j];
                                }
                            }
                            
                        ImProcFunctions::Median medianTypeL = Median::TYPE_3X3_SOFT;

                        int itera = 1;
                        if(denstr < 0.2f) {
                            medianTypeL = Median::TYPE_3X3_SOFT;
                            itera = 1;
                        } else if (denstr < 0.35f) {
                            medianTypeL = Median::TYPE_3X3_STRONG;
                            itera = 2;
                        } else if (denstr < 0.45f) {
                            medianTypeL = Median::TYPE_3X3_STRONG;
                            itera = 4;
                        } else if (denstr < 0.6f) {
                            medianTypeL = Median::TYPE_5X5_STRONG;
                            itera = 4;
                        } else if (denstr < 0.7f) {
                            medianTypeL = Median::TYPE_5X5_STRONG;
                            itera = 6;
                        } else if (denstr < 0.8f) {//slow
                            medianTypeL = Median::TYPE_7X7;
                            itera = 3;
                        } else if (denstr < 0.9f) {
                            medianTypeL = Median::TYPE_7X7;
                            itera = 4;
                        } else {//very slow
                            medianTypeL = Median::TYPE_9X9;
                            itera = 3;            
                        }
                        ImProcFunctions::Median_Denoise(mR, mR, bfw, bfh, medianTypeL , itera, false, tmL);
                        ImProcFunctions::Median_Denoise(mG, mG, bfw, bfh, medianTypeL , itera, false, tmL);
                        ImProcFunctions::Median_Denoise(mB, mB, bfw, bfh, medianTypeL , itera, false, tmL);
#ifdef _OPENMP
    #pragma omp parallel for schedule(dynamic, 16)
#endif
                        for (int i = 0; i < bfh; ++i) {
                            for (int j = 0; j < bfw; ++j) {
                                redVals[i][j] = intp(denstr, mR[i][j], redVals[i][j]); 
                                greenVals[i][j] = intp(denstr, mG[i][j], greenVals[i][j]); 
                                blueVals[i][j] = intp(denstr, mB[i][j], blueVals[i][j]); 
                            }
                        }
                    }

                    float s_scale = std::sqrt(sk);
                    float contrast = pow_F(denoco / 100.f, 1.f) * s_scale;
                  
                    array2D<float> Y (bfw, bfh);
            
                    for (int i = 0; i < bfh; ++i) {
                        Color::RGB2L(redVals[i], greenVals[i], blueVals[i], Y[i], wip, bfw);
                    }
                    float reducautocontrast = 1.f;//to take noise into account
                    buildBlendMask2(Y, clipMask, bfw, bfh, contrast, 1.f, autode, 2.f / s_scale, 1.f, reducautocontrast);
               
                    denocont = 100.f * pow_F(contrast, 1.f)/ s_scale;
                             
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                        for (int ir = 0; ir < bfh; ir++) {
                            for (int jr = 0; jr < bfw; jr++) {
                                bufwv.L[ir][jr] = intp(lp.denorati * clipMask[ir][jr], bufwv4.L[ir][jr], bufwv.L[ir][jr]);
                                bufwv.a[ir][jr] = intp(lp.denorati * clipMask[ir][jr], bufwv4.a[ir][jr], bufwv.a[ir][jr]);
                                bufwv.b[ir][jr] = intp(lp.denorati * clipMask[ir][jr], bufwv4.b[ir][jr], bufwv.b[ir][jr]);
                            }
                        } 
                }       
            //end denoise with contrast threshold mask


                    DeNoise_Local2(lp,  originalmaskbl, levred, huerefblur, lumarefblur, chromarefblur, original, transformed, bufwv, cx, cy, sk);
                } else {
                    DeNoise_Local2(lp,  original, levred, huerefblur, lumarefblur, chromarefblur, original, transformed, bufwv, cx, cy, sk);
                }
            }
        }
 
    }

}

float triangle(float a, float a1, float b)
{
    if (a != b) {
        float b1;
        float a2 = a1 - a;

        if (b < a) {
            b1 = b + a2 *      b  /     a ;
        } else       {
            b1 = b + a2 * (65535.f - b) / (65535.f - a);
        }

        return b1;
    }

    return a1;
}

void rgbtone(float& maxval, float& medval, float& minval, const LUTf& lutToneCurve)
{
    float minvalold = minval, medvalold = medval, maxvalold = maxval;

    maxval = lutToneCurve[maxvalold];
    minval = lutToneCurve[minvalold];
    medval = minval + ((maxval - minval) * (medvalold - minvalold) / (maxvalold - minvalold));
}

void ImProcFunctions::clarimerge(const struct local_params& lp, float &mL, float &mC, bool &exec, LabImage *tmpresid, int wavelet_level, int sk, int numThreads)
{
    if (mL != 0.f && mC == 0.f) {
        mC = 0.0001f;
        exec = true;
    }

    if (mC != 0.f && mL == 0.f) {
        mL = 0.0001f;
        exec = true;
    }

    if (mL != 0.f && mC != 0.f) {
        exec = true;
    }

    if (mL != 0.f) {

        wavelet_decomposition *wdspotresid = new wavelet_decomposition(tmpresid->L[0], tmpresid->W, tmpresid->H, wavelet_level, 1, sk, numThreads, lp.daubLen);

        if (wdspotresid->memory_allocation_failed()) {
            return;
        }

        int maxlvlresid = wdspotresid->maxlevel();

        if (maxlvlresid > 4) {//Clarity
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic) collapse(2)
#endif

            for (int dir = 1; dir < 4; dir++) {
                for (int level = 0; level < maxlvlresid; ++level) {
                    int W_L = wdspotresid->level_W(level);
                    int H_L = wdspotresid->level_H(level);
                    float* const* wav_Lresid = wdspotresid->level_coeffs(level);

                    for (int i = 0; i < W_L * H_L; i++) {
                        wav_Lresid[dir][i] = 0.f;
                    }
                }
            }
        } else {//Sharp
            float *wav_L0resid = wdspotresid->get_coeff0();
            int W_L = wdspotresid->level_W(0);
            int H_L = wdspotresid->level_H(0);

            for (int i = 0; i < W_L * H_L; i++) {
                wav_L0resid[i] = 0.f;
            }
        }

        wdspotresid->reconstruct(tmpresid->L[0], 1.f);
        delete wdspotresid;
    }


    if (mC != 0.f) {

        wavelet_decomposition *wdspotresida = new wavelet_decomposition(tmpresid->a[0], tmpresid->W, tmpresid->H, wavelet_level, 1, sk, numThreads, lp.daubLen);

        if (wdspotresida->memory_allocation_failed()) {
            return;
        }

        int maxlvlresid = wdspotresida->maxlevel();

        if (maxlvlresid > 4) {//Clarity
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic) collapse(2)
#endif

            for (int dir = 1; dir < 4; dir++) {
                for (int level = 0; level < maxlvlresid; ++level) {
                    int W_L = wdspotresida->level_W(level);
                    int H_L = wdspotresida->level_H(level);
                    float* const* wav_Lresida = wdspotresida->level_coeffs(level);

                    for (int i = 0; i < W_L * H_L; i++) {
                        wav_Lresida[dir][i] = 0.f;
                    }
                }
            }
        } else {//Sharp
            float *wav_L0resida = wdspotresida->get_coeff0();
            int W_L = wdspotresida->level_W(0);
            int H_L = wdspotresida->level_H(0);

            for (int i = 0; i < W_L * H_L; i++) {
                wav_L0resida[i] = 0.f;
            }
        }

        wdspotresida->reconstruct(tmpresid->a[0], 1.f);
        delete wdspotresida;

        wavelet_decomposition *wdspotresidb = new wavelet_decomposition(tmpresid->b[0], tmpresid->W, tmpresid->H, wavelet_level, 1, sk, numThreads, lp.daubLen);

        if (wdspotresidb->memory_allocation_failed()) {
            return;
        }

        maxlvlresid = wdspotresidb->maxlevel();

        if (maxlvlresid > 4) {//Clarity
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic) collapse(2)
#endif

            for (int dir = 1; dir < 4; dir++) {
                for (int level = 0; level < maxlvlresid; ++level) {
                    int W_L = wdspotresidb->level_W(level);
                    int H_L = wdspotresidb->level_H(level);
                    float* const* wav_Lresidb = wdspotresidb->level_coeffs(level);

                    for (int i = 0; i < W_L * H_L; i++) {
                        wav_Lresidb[dir][i] = 0.f;
                    }
                }
            }
        } else {//Sharp
            float *wav_L0residb = wdspotresidb->get_coeff0();
            int W_L = wdspotresidb->level_W(0);
            int H_L = wdspotresidb->level_H(0);

            for (int i = 0; i < W_L * H_L; i++) {
                wav_L0residb[i] = 0.f;
            }
        }

        wdspotresidb->reconstruct(tmpresid->b[0], 1.f);
        delete wdspotresidb;
    }
}

void ImProcFunctions::avoidcolshi(const struct local_params& lp, int sp, LabImage *transformed, LabImage *reserved, int cy, int cx, int sk)
{
    int avoidgamut = 0;

    if (params->locallab.spots.at(sp).avoidgamutMethod == "NONE") {
        avoidgamut = 0;
    } else if (params->locallab.spots.at(sp).avoidgamutMethod == "LAB") {
        avoidgamut = 1;
    } else if (params->locallab.spots.at(sp).avoidgamutMethod == "XYZ") {
        avoidgamut = 2;
    } else if (params->locallab.spots.at(sp).avoidgamutMethod == "XYZREL") {
        avoidgamut = 3;
    } else if (params->locallab.spots.at(sp).avoidgamutMethod == "MUNS") {
        avoidgamut = 4;
    }

    if (avoidgamut == 0) {
        return;
    }

    if (avoidgamut > 0  && lp.islocal) {
        const float ach = lp.trans / 100.f;
        bool execmunsell = true;

        if (params->locallab.spots.at(sp).expcie && (params->locallab.spots.at(sp).modecam == "jz" || params->locallab.spots.at(sp).modecam == "cam16")) {
            execmunsell = false;
        }

        TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile);
        const double wip[3][3] = {//improve precision with double
            {wiprof[0][0], wiprof[0][1], wiprof[0][2]},
            {wiprof[1][0], wiprof[1][1], wiprof[1][2]},
            {wiprof[2][0], wiprof[2][1], wiprof[2][2]}
        };

        TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
        const double wp[3][3] = {//improve precision with double
            {wprof[0][0], wprof[0][1], wprof[0][2]},
            {wprof[1][0], wprof[1][1], wprof[1][2]},
            {wprof[2][0], wprof[2][1], wprof[2][2]}
        };

        const float softr = params->locallab.spots.at(sp).avoidrad;//max softr = 30
        //   const bool muns = params->locallab.spots.at(sp).avoidmun;//Munsell control with 200 LUT
        //improve precision with mint and maxt
        const float tr = std::min(2.f, softr);
        const float mint = 0.15f - 0.06f * tr;//between 0.15f and 0.03f
        const float maxt = 0.98f + 0.008f * tr;//between 0.98f and 0.996f

        const bool highlight = params->toneCurve.hrenabled;
        const bool needHH =  true; //always Munsell to avoid bad behavior //(lp.chro != 0.f);
#ifdef _OPENMP
        #pragma omp parallel if (multiThread)
#endif
        {
#if defined(__SSE2__) || defined(RT_SIMDE)
            float atan2Buffer[transformed->W] ALIGNED16;
            float sqrtBuffer[transformed->W] ALIGNED16;
            float sincosyBuffer[transformed->W] ALIGNED16;
            float sincosxBuffer[transformed->W] ALIGNED16;
            vfloat c327d68v = F2V(327.68f);
            vfloat onev = F2V(1.f);
#endif

#ifdef _OPENMP
            #pragma omp for schedule(dynamic,16)
#endif

            for (int y = 0; y < transformed->H; y++) {
                const int loy = cy + y;
                const bool isZone0 = loy > lp.yc + lp.ly || loy < lp.yc - lp.lyT; // whole line is zone 0 => we can skip a lot of processing

                if (isZone0) { // outside selection and outside transition zone => no effect, keep original values
                    continue;
                }

#if defined(__SSE2__) || defined(RT_SIMDE)
                int i = 0;

                for (; i < transformed->W - 3; i += 4) {
                    vfloat av = LVFU(transformed->a[y][i]);
                    vfloat bv = LVFU(transformed->b[y][i]);

                    if (needHH) { // only do expensive atan2 calculation if needed
                        STVF(atan2Buffer[i], xatan2f(bv, av));
                    }

                    vfloat Chprov1v = vsqrtf(SQRV(bv) + SQRV(av));
                    STVF(sqrtBuffer[i], Chprov1v / c327d68v);
                    vfloat sincosyv = av / Chprov1v;
                    vfloat sincosxv = bv / Chprov1v;
                    vmask selmask = vmaskf_eq(Chprov1v, ZEROV);
                    sincosyv = vself(selmask, onev, sincosyv);
                    sincosxv = vselfnotzero(selmask, sincosxv);
                    STVF(sincosyBuffer[i], sincosyv);
                    STVF(sincosxBuffer[i], sincosxv);
                }

                for (; i < transformed->W; i++) {
                    float aa = transformed->a[y][i];
                    float bb = transformed->b[y][i];

                    if (needHH) { // only do expensive atan2 calculation if needed
                        atan2Buffer[i] = xatan2f(bb, aa);
                    }

                    float Chprov1 = std::sqrt(SQR(bb) + SQR(aa));
                    sqrtBuffer[i] = Chprov1 / 327.68f;

                    if (Chprov1 == 0.0f) {
                        sincosyBuffer[i] = 1.f;
                        sincosxBuffer[i] = 0.0f;
                    } else {
                        sincosyBuffer[i] = aa / Chprov1;
                        sincosxBuffer[i] = bb / Chprov1;
                    }
                }

#endif

                for (int x = 0; x < transformed->W; x++) {
                    int lox = cx + x;
                    int zone;
                    float localFactor = 1.f;

                    if (lp.shapmet == 0) {
                        calcTransition(lox, loy, ach, lp, zone, localFactor);
                    } else { /*if (lp.shapmet == 1)*/
                        calcTransitionrect(lox, loy, ach, lp, zone, localFactor);
                    }

                    if (zone == 0) { // outside selection and outside transition zone => no effect, keep original values
                        continue;
                    }

                    float Lprov1 = transformed->L[y][x] / 327.68f;
                    float2 sincosval;
#if defined(__SSE2__) || defined(RT_SIMDE)
                    float HH = atan2Buffer[x]; // reading HH from line buffer even if line buffer is not filled is faster than branching
                    float Chprov1 = sqrtBuffer[x];
                    sincosval.y = sincosyBuffer[x];
                    sincosval.x = sincosxBuffer[x];
                    float chr = 0.f;

#else
                    const float aa = transformed->a[y][x];
                    const float bb = transformed->b[y][x];
                    float HH = 0.f, chr = 0.f;

                    if (needHH) { // only do expensive atan2 calculation if needed
                        HH = xatan2f(bb, aa);
                    }

                    float Chprov1 = std::sqrt(SQR(aa) + SQR(bb)) / 327.68f;

                    if (Chprov1 == 0.0f) {
                        sincosval.y = 1.f;
                        sincosval.x = 0.0f;
                    } else {
                        sincosval.y = aa / (Chprov1 * 327.68f);
                        sincosval.x = bb / (Chprov1 * 327.68f);
                    }

#endif
                    float lnew = transformed->L[y][x];
                    float anew = transformed->a[y][x];
                    float bnew = transformed->b[y][x];
                    Lprov1 = lnew / 327.68f;
                    //HH = xatan2f(bnew, anew);

                    if (avoidgamut == 1) { //Lab correction

                        Color::pregamutlab(Lprov1, HH, chr);
                        Chprov1 = rtengine::min(Chprov1, chr);

                        float R, G, B;
                        Color::gamutLchonly(HH, sincosval, Lprov1, Chprov1, R, G, B, wip, highlight, mint, maxt);//replace for best results
                        lnew = Lprov1 * 327.68f;
                        anew = 327.68f * Chprov1 * sincosval.y;
                        bnew = 327.68f * Chprov1 * sincosval.x;
                        //HH = xatan2f(bnew, anew);
                        transformed->a[y][x] = anew;
                        transformed->b[y][x] = bnew;

                    } else if (avoidgamut == 2  || avoidgamut == 3) { //XYZ correction
                        float xg, yg, zg;
                        const float aag = transformed->a[y][x];//anew
                        const float bbg = transformed->b[y][x];//bnew
                        float Lag = transformed->L[y][x];

                        Color::Lab2XYZ(Lag, aag, bbg, xg, yg, zg);
                        float x0 = xg;
                        float y0 = yg;
                        float z0 = zg;

                        Color::gamutmap(xg, yg, zg, wp);

                        if (avoidgamut == 3) {//-0.3f arbitrary coeff, 04-2024 change mistake where 0.5f => -0.3f
                            xg = rtengine::LIM(xg - 0.3f * (x0 - xg), 0.f, 65535.f);
                            yg = rtengine::LIM(yg - 0.3f * (y0 - yg), 0.f, 65535.f);
                            zg = rtengine::LIM(zg - 0.3f * (z0 - zg), 0.f, 65535.f);
                        }


                        //Color::gamutmap(xg, yg, zg, wp);//Put XYZ in gamut wp
                        float aag2, bbg2;
                        Color::XYZ2Lab(xg, yg, zg, Lag, aag2, bbg2);
                        Lprov1 = Lag / 327.68f;
                        HH = xatan2f(bbg2, aag2);//rebuild HH in case of...absolute colorimetry
                        Chprov1 = std::sqrt(SQR(aag2) + SQR(bbg2)) / 327.68f;

                        if (Chprov1 == 0.0f) {
                            sincosval.y = 1.f;
                            sincosval.x = 0.0f;
                        } else {
                            sincosval.y = aag2 / (Chprov1 * 327.68f);
                            sincosval.x = bbg2 / (Chprov1 * 327.68f);
                        }

                        lnew = Lprov1 * 327.68f;
                        anew = 327.68f * Chprov1 * sincosval.y;
                        bnew = 327.68f * Chprov1 * sincosval.x;
                        transformed->a[y][x] = anew;
                        transformed->b[y][x] = bnew;

                    }

                    if (needHH && avoidgamut <= 4) {//Munsell
                        Lprov1 = lnew / 327.68f;
                        float Chprov = sqrt(SQR(anew) + SQR(bnew)) / 327.68f;

                        const float Lprov2 = reserved->L[y][x] / 327.68f;
                        float correctionHue = 0.f; // Munsell's correction
                        float correctlum = 0.f;
                        const float memChprov = std::sqrt(SQR(reserved->a[y][x]) + SQR(reserved->b[y][x])) / 327.68f;


                        if (execmunsell) {
                            Color::AllMunsellLch(true, Lprov1, Lprov2, HH, Chprov, memChprov, correctionHue, correctlum);
                        }

                        if (correctionHue != 0.f || correctlum != 0.f) {

                            if (std::fabs(correctionHue) < 0.015f) {
                                HH += correctlum;    // correct only if correct Munsell chroma very small.
                            }

                            sincosval = xsincosf(HH + correctionHue);
                        }

                        anew = 327.68f * Chprov * sincosval.y; // apply Munsell
                        bnew = 327.68f * Chprov * sincosval.x;
                        transformed->a[y][x] = anew; // apply Munsell
                        transformed->b[y][x] = bnew;
                    }
                }
            }
        }

        //Guidedfilter to reduce artifacts in transitions : case Lab
        if (softr != 0.f && avoidgamut == 1) {//soft for L a b because we change color...
            const float tmpblur = softr < 0.f ? -1.f / softr : 1.f + softr;
            const int r1 = rtengine::max<int>(6 / sk * tmpblur + 0.5f, 1);
            const int r2 = rtengine::max<int>(10 / sk * tmpblur + 0.5f, 1);

            constexpr float epsilmax = 0.005f;
            constexpr float epsilmin = 0.00001f;

            constexpr float aepsil = (epsilmax - epsilmin) / 100.f;
            constexpr float bepsil = epsilmin;
            const float epsil = softr < 0.f ? 0.001f : aepsil * softr + bepsil;

            const int bw = transformed->W;
            const int bh = transformed->H;
            array2D<float> ble(bw, bh);
            array2D<float> guid(bw, bh);

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bh ; y++) {
                for (int x = 0; x < bw; x++) {
                    ble[y][x] = transformed->L[y][x] / 32768.f;
                    guid[y][x] = reserved->L[y][x] / 32768.f;
                }
            }

            rtengine::guidedFilter(guid, ble, ble, r2, 0.2f * epsil, multiThread);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bh; y++) {
                for (int x = 0; x < bw; x++) {
                    transformed->L[y][x] = 32768.f * ble[y][x];
                }
            }

            array2D<float> &blechro = ble; // reuse buffer
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bh ; y++) {
                for (int x = 0; x < bw; x++) {
                    blechro[y][x] = std::sqrt(SQR(transformed->b[y][x]) + SQR(transformed->a[y][x])) / 32768.f;
                }
            }

            rtengine::guidedFilter(guid, blechro, blechro, r1, epsil, multiThread);

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bh; y++) {
                for (int x = 0; x < bw; x++) {
                    const float Chprov1 = std::sqrt(SQR(transformed->a[y][x]) + SQR(transformed->b[y][x]));
                    float2  sincosval;

                    if (Chprov1 == 0.0f) {
                        sincosval.y = 1.f;
                        sincosval.x = 0.0f;
                    } else {
                        sincosval.y = transformed->a[y][x] / Chprov1;
                        sincosval.x = transformed->b[y][x] / Chprov1;
                    }

                    transformed->a[y][x] = 32768.f * blechro[y][x] * sincosval.y;
                    transformed->b[y][x] = 32768.f * blechro[y][x] * sincosval.x;
                }
            }
        }
    }
}

void maskrecov(const LabImage * bufcolfin, LabImage * original, LabImage * bufmaskblurcol, int bfh, int bfw, int ystart, int xstart, float hig, float low, float recoth, float decay, bool invmask, int sk, bool multiThread)
{
    LabImage tmp3(bfw, bfh);

    for (int y = 0; y < bfh; y++) {
        for (int x = 0; x < bfw; x++) {
            tmp3.L[y][x] = original->L[y + ystart][x + xstart];
            tmp3.a[y][x] = original->a[y + ystart][x + xstart];
            tmp3.b[y][x] = original->b[y + ystart][x + xstart];
        }
    }

    array2D<float> masklum;
    masklum(bfw, bfh);

    for (int ir = 0; ir < bfh; ir++)
        for (int jr = 0; jr < bfw; jr++) {
            masklum[ir][jr] = 1.f;
        }

    float higc;
    calcdif(hig, higc);
    float lowc;
    calcdif(low, lowc);

    if (higc < lowc) {
        higc = lowc + 0.01f;
    }

    float th = (recoth - 1.f);
    float ahigh = th / (higc - 100.f);
    float bhigh = 1.f - higc * ahigh;

    float alow = th / lowc;
    float blow = 1.f - th;
#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int ir = 0; ir < bfh; ir++) {
        for (int jr = 0; jr < bfw; jr++) {
            const float lM = bufmaskblurcol->L[ir][jr];
            const float lmr = lM / 327.68f;

            if (lM < 327.68f * lowc) {
                masklum[ir][jr] = alow * lmr + blow;
            } else if (lM < 327.68f * higc) {
                //nothing...but we can..
            } else {
                masklum[ir][jr] = ahigh * lmr + bhigh;
            }

            float k = masklum[ir][jr];

            if (invmask == false) {
                masklum[ir][jr] = 1 - pow(k, decay);
            } else {
                masklum[ir][jr] = pow(k, decay);
            }

        }
    }

    for (int i = 0; i < 3; ++i) {
        boxblur(static_cast<float**>(masklum), static_cast<float**>(masklum), 10 / sk, bfw, bfh, false);
    }

#ifdef _OPENMP
    #pragma omp parallel for if (multiThread)
#endif

    for (int i = 0; i < bfh; ++i) {
        for (int j = 0; j < bfw; ++j) {
            bufcolfin->L[i][j] = (tmp3.L[i][j] - bufcolfin->L[i][j]) *  LIM01(masklum[i][j]) + bufcolfin->L[i][j];
            bufcolfin->a[i][j] = (tmp3.a[i][j] - bufcolfin->a[i][j]) *  LIM01(masklum[i][j]) + bufcolfin->a[i][j];
            bufcolfin->b[i][j] = (tmp3.b[i][j] - bufcolfin->b[i][j]) *  LIM01(masklum[i][j]) + bufcolfin->b[i][j];
        }
    }

    masklum.free();
}

//thanks to Alberto Griggio
void ImProcFunctions::detail_mask(const array2D<float> &src, array2D<float> &mask, int bfw, int bfh, float scaling, float threshold, float ceiling, float factor, BlurType blur_type, float blur, bool multithread)
{
    const int W = bfw;
    const int H = bfh;
    mask(W, H);

    array2D<float> L2(W / 4, H / 4); //ARRAY2D_ALIGNED);
    array2D<float> m2(W / 4, H / 4); //ARRAY2D_ALIGNED)
    rescaleBilinear(src, L2, multithread);
#ifdef _OPENMP
    #   pragma omp parallel for if (multithread)
#endif

    for (int y = 0; y < H / 4; ++y) {
        for (int x = 0; x < W / 4; ++x) {
            L2[y][x] = xlin2log(L2[y][x] / scaling, 50.f);
        }
    }

    laplacian(L2, m2, W / 4, H / 4, threshold / scaling, ceiling / scaling, factor, multithread);

    rescaleBilinear(m2, mask, multithread);

    const auto scurve =
    [](float x) -> float {
        constexpr float b = 101.f;
        constexpr float a = 2.23f;
        return xlin2log(pow_F(x, a), b);
    };

    const float thr = 1.f - factor;
#ifdef _OPENMP
    #   pragma omp parallel for if (multithread)
#endif

    for (int y = 0; y < H; ++y) {
        for (int x = 0; x < W; ++x) {
            mask[y][x] = scurve(LIM01(mask[y][x] + thr));
        }
    }

    if (blur_type == BlurType::GAUSS) {

#ifdef _OPENMP
        #       pragma omp parallel if (multithread)
#endif
        {
            gaussianBlur(mask, mask, W, H, blur);
        }
    } else if (blur_type == BlurType::BOX) {
        if (int(blur) > 0) {
            for (int i = 0; i < 3; ++i) {
                boxblur(static_cast<float**>(mask), static_cast<float**>(mask), blur, W, H, multithread);
            }
        }
    }

}

// basic idea taken from Algorithm 3 in the paper:
// "Parameter-Free Fast Pixelwise Non-Local Means Denoising" http://www.ipol.im/pub/art/2014/120/
// by Jacques Froment

// thanks to Alberto Griggio for this wonderful code
// thanks to Ingo Weyrich <heckflosse67@gmx.de> for many speedup suggestions!
// adapted to Rawtherapee Local adjustments J.Desmis january 2021
//

void ImProcFunctions::NLMeans(float **img, int strength, int detail_thresh, int patch, int radius, float gam, int bfw, int bfh, float scale, bool multithread)
{
    if (!strength) {
        return;
    }

    if (scale > 5.f) { //avoid to small values - leads to crash - but enough to evaluate noise
        return;
    }
   // BENCHFUN
    const int W = bfw;
    const int H = bfh;
    float gamma = gam;
    rtengine::GammaValues g_a; //gamma parameters
    double pwr = 1.0 / static_cast<double>(gam);//default 3.0 - gamma Lab
    double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
    rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

    //first change Lab L to pseudo linear with gamma = 3.f slope 9.032...and in range 0...65536, or with gamma slope Lab

#ifdef _OPENMP
    #   pragma omp parallel for schedule(dynamic,16) if (multithread)
#endif

    for (int y = 0; y < H; ++y) {
        int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

        for (; x < W - 3; x += 4) {
            STVFU(img[y][x], F2V(65536.f) * igammalog(LVFU(img[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[2]), F2V(g_a[4])));
        }

#endif

        for (; x < W; ++x) {
            img[y][x] = 65536.f * igammalog(img[y][x] / 32768.f, gamma, ts, g_a[2], g_a[4]);
        }
    }

    // these two can be changed if needed. increasing max_patch_radius doesn't
    // affect performance, whereas max_search_radius *really* does
    // (the complexity is O(max_search_radius^2 * W * H))
//    constexpr int max_patch_radius = 2;
//    constexpr int max_search_radius = 5;
    int max_patch_radius = patch;
    int max_search_radius = radius;

    const int search_radius = int(std::ceil(float(max_search_radius) / scale));
    const int patch_radius = int(std::ceil(float(max_patch_radius) / scale));

    // the strength parameter controls the scaling of the weights
    // (called h^2 in the papers)
    float eps = 1e-6f;//to avoid too low values and divide near by zero...when  scale > 1
    const float h2 = eps + SQR(std::pow(float(strength) / 100.f, 0.9f) / 30.f / scale);
    // this is the main difference between our version and more conventional
    // nl-means implementations: instead of varying the patch size, we control
    // the detail preservation by using a varying weight scaling for the
    // pixels, depending on our estimate of how much details there are in the
    // pixel neighborhood. We do this by computing a "detail mask", using a
    // laplacian filter with additional averaging and smoothing. The
    // detail_thresh parameter controls the degree of detail preservation: the
    // (averaged, smoothed) laplacian is first normalized to [0,1], and then
    // modified by compression and offsetting depending on the detail_thresh
    // parameter, i.e. mask[y][x] = mask[y][x] * (1 - f) + f,
    // where f = detail_thresh / 100
    float amount = LIM(float(detail_thresh) / 100.f, 0.f, 0.99f);
    array2D<float> mask(W, H);// ARRAY2D_ALIGNED);

    {
        array2D<float> LL(W, H, img, ARRAY2D_BYREFERENCE);
        ImProcFunctions::detail_mask(LL, mask, W, H, 1.f, 1e-3f, 1.f, amount, BlurType::GAUSS, 2.f / scale, multithread);

    }

//allocate dst - same type of datas as img
    float** dst;
    int wid = W;
    int hei = H;
    dst = new float*[hei];

    for (int i = 0; i < hei; ++i) {
        dst[i] = new float[wid];
    }

    const int border = search_radius + patch_radius;
    const int WW = W + border * 2;
    const int HH = H + border * 2;

    array2D<float> src(WW, HH);//, ARRAY2D_ALIGNED);

#ifdef _OPENMP
    #   pragma omp parallel for if (multithread)
#endif

    for (int y = 0; y < HH; ++y) {
        int yy = y <= border ? 0 : y - border >= H ? H - 1 : y - border;

        for (int x = 0; x < WW; ++x) {
            int xx = x <= border ? 0 : x - border >= W ? W - 1 : x - border;
            float Y = img[yy][xx] / 65536.f;
            src[y][x] = Y;
        }
    }

#ifdef _OPENMP
    #   pragma omp parallel for if (multithread)
#endif

    for (int y = 0; y < H; ++y) {
        for (int x = 0; x < W; ++x) {
            dst[y][x] = 0.f;
        }
    }

    constexpr int lutsz = 8192;
    constexpr float lutfactor = 100.f / float(lutsz - 1);
    LUTf explut(lutsz);

    for (int i = 0; i < lutsz; ++i) {
        float x = float(i) * lutfactor;
        explut[i] = xexpf(-x);
    }

#ifdef _OPENMP
    #   pragma omp parallel for if (multithread)
#endif

    for (int y = 0; y < H; ++y) {
        for (int x = 0; x < W; ++x) {
            mask[y][x] = (1.f / (mask[y][x] * h2)) / lutfactor;
        }
    }

    // process by tiles to avoid numerical accuracy errors in the computation
    // of the integral image
    const int tile_size = 150;
    const int ntiles_x = int(std::ceil(float(WW) / (tile_size - 2 * border)));
    const int ntiles_y = int(std::ceil(float(HH) / (tile_size - 2 * border)));
    const int ntiles = ntiles_x * ntiles_y;

#if defined(__SSE2__) || defined(RT_SIMDE)
    const vfloat zerov = F2V(0.0);
    const vfloat v1e_5f = F2V(1e-5f);
    const vfloat v65536f = F2V(65536.f);
#endif

#ifdef _OPENMP
    #pragma omp parallel if (multithread)
#endif
    {

#if defined(__SSE2__) || (defined(RT_SIMDE) && SIMDE_VERSION_CHECK(0, 8, 0))
        // flush denormals to zero to avoid performance penalty
        const auto oldMode = _MM_GET_FLUSH_ZERO_MODE();
        _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
#endif

#ifdef _OPENMP
        #pragma omp for schedule(dynamic, 2)
#endif

        for (int tile = 0; tile < ntiles; ++tile) {
            const int tile_y = tile / ntiles_x;
            const int tile_x = tile % ntiles_x;

            const int start_y = tile_y * (tile_size - 2 * border);
            const int end_y = std::min(start_y + tile_size, HH);
            const int TH = end_y - start_y;

            const int start_x = tile_x * (tile_size - 2 * border);
            const int end_x = std::min(start_x + tile_size, WW);
            const int TW = end_x - start_x;

            const auto Yf = [ = ](int y) -> int { return LIM(y + start_y, 0, HH - 1); };
            const auto Xf = [ = ](int x) -> int { return LIM(x + start_x, 0, WW - 1); };

            const auto score =
            [&](int tx, int ty, int zx, int zy) -> float {
                return SQR(src[Yf(zy)][Xf(zx)] - src[Yf(zy + ty)][Xf(zx + tx)]);
            };

            array2D<float> St(TW, TH);//, ARRAY2D_ALIGNED);
            array2D<float> SW(TW, TH, ARRAY2D_CLEAR_DATA);//, ARRAY2D_ALIGNED|ARRAY2D_CLEAR_DATA);

            for (int ty = -search_radius; ty <= search_radius; ++ty) {
                for (int tx = -search_radius; tx <= search_radius; ++tx) {
                    // Step 1 — Compute the integral image St
                    St[0][0] = 0.f;

                    for (int xx = 1; xx < TW; ++xx) {
                        St[0][xx] = St[0][xx - 1] + score(tx, ty, xx, 0);
                    }

                    for (int yy = 1; yy < TH; ++yy) {
                        St[yy][0] = St[yy - 1][0] + score(tx, ty, 0, yy);
                    }

                    for (int yy = 1; yy < TH; ++yy) {
                        for (int xx = 1; xx < TW; ++xx) {
                            // operation grouping tuned for performance (empirically)
                            St[yy][xx] = (St[yy][xx - 1] + St[yy - 1][xx]) - (St[yy - 1][xx - 1] - score(tx, ty, xx, yy));
                        }
                    }

                    // Step 2 — Compute weight and estimate for patches
                    // V(x), V(y) with y = x + t
                    for (int yy = start_y + border; yy < end_y - border; ++yy) {
                        int y = yy - border;
                        int xx = start_x + border;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; xx < end_x - border - 3; xx += 4) {
                            int x = xx - border;
                            int sx = xx + tx;
                            int sy = yy + ty;

                            int sty = yy - start_y;
                            int stx = xx - start_x;

                            vfloat dist2 = LVFU(St[sty + patch_radius][stx + patch_radius]) + LVFU(St[sty - patch_radius][stx - patch_radius]) - LVFU(St[sty + patch_radius][stx - patch_radius]) - LVFU(St[sty - patch_radius][stx + patch_radius]);
                            dist2 = vmaxf(dist2, zerov);
                            vfloat d = dist2 * LVFU(mask[y][x]);
                            vfloat weight = explut[d];
                            STVFU(SW[y - start_y][x - start_x], LVFU(SW[y - start_y][x - start_x]) + weight);
                            vfloat Y = weight * LVFU(src[sy][sx]);
                            STVFU(dst[y][x], LVFU(dst[y][x]) + Y);
                        }

#endif

                        for (; xx < end_x - border; ++xx) {
                            int x = xx - border;
                            int sx = xx + tx;
                            int sy = yy + ty;

                            int sty = yy - start_y;
                            int stx = xx - start_x;

                            float dist2 = St[sty + patch_radius][stx + patch_radius] + St[sty - patch_radius][stx - patch_radius] - St[sty + patch_radius][stx - patch_radius] - St[sty - patch_radius][stx + patch_radius];
                            dist2 = std::max(dist2, 0.f);
                            float d = dist2 * mask[y][x];
                            float weight = explut[d];
                            SW[y - start_y][x - start_x] += weight;
                            float Y = weight * src[sy][sx];
                            dst[y][x] += Y;

                            assert(!xisinff(dst[y][x]));
                            assert(!xisnanf(dst[y][x]));
                        }
                    }
                }
            }


            // Compute final estimate at pixel x = (x1, x2)
            for (int yy = start_y + border; yy < end_y - border; ++yy) {
                int y = yy - border;
                int xx = start_x + border;
#if defined(__SSE2__) || defined(RT_SIMDE)

                for (; xx < end_x - border - 3; xx += 4) {
                    int x = xx - border;

                    const vfloat Y = LVFU(dst[y][x]);
                    const vfloat f = (v1e_5f + LVFU(SW[y - start_y][x - start_x]));
                    STVFU(dst[y][x], (Y / f) * v65536f);
                }

#endif

                for (; xx < end_x - border; ++xx) {
                    int x = xx - border;

                    const float Y = dst[y][x];
                    const float f = (1e-5f + SW[y - start_y][x - start_x]);
                    dst[y][x] = (Y / f) * 65536.f;

                    assert(!xisnanf(dst[y][x]));
                }
            }
        }

#if defined(__SSE2__) || (defined(RT_SIMDE) && SIMDE_VERSION_CHECK(0, 8, 0))
        _MM_SET_FLUSH_ZERO_MODE(oldMode);
#endif

    } // omp parallel

#ifdef _OPENMP
    #   pragma omp parallel for schedule(dynamic,16) if (multithread)
#endif

    for (int y = 0; y < H; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
        int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

        for (; x < W - 3; x += 4) {
            STVFU(img[y][x], F2V(32768.f) * gammalog(LVFU(dst[y][x]) / F2V(65536.f), F2V(gamma), F2V(ts), F2V(g_a[3]), F2V(g_a[4])));
        }

#endif

        for (; x < W; ++x) {
            img[y][x] = 32768.f * gammalog(dst[y][x] / 65536.f, gamma, ts, g_a[3], g_a[4]);
        }
    }

    for (int i = 0; i < hei; ++i) {
        delete[] dst[i];
    }

    delete[] dst;

}

// GHT filter ported from Siril.
// 
// see https://siril.org/tutorials/ghs/ for more info
// 
// Copyright of the original code follows
/*
 * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
 * Copyright (C) 2012-2023 team free-astro (see more in AUTHORS file)
 * Reference site is https://free-astro.org/index.php/Siril
 *
 * Siril 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.
 *
 * Siril 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 Siril. If not, see <http://www.gnu.org/licenses/>.
 */
/*
//Copyright algorithm Pixlnsight David Payne 2021
https://www.ghsastro.co.uk/doc/tools/GeneralizedHyperbolicStretch/GeneralizedHyperbolicStretch.html#__Description_:_About_GHS__
*/
/*
 * Thanks to Alberto Griggio for the code CTL ght.ctl
*/

/*
https://www.ghsastro.co.uk/doc/tools/GeneralizedHyperbolicStretch/GeneralizedHyperbolicStretch.html#equationLabel

Summary of calculations made during GHS


5.2.1 Definition of variables
D = e(Stretch factor) - 1
b = Local intensity
SP = Symmetry point
LP = Protect shadows
HP = Protect highlights

5.2.2 Base transformation equations
The base transformation for each transformation type is defined by T : x → T(x) in the following table. The table also shows the first derivative of T, denoted T', as this is needed to build the full transformation.

Generalised hyperbolic
Exponential
b = 0
T ->1 - e-D.x
T'->D.e-D.x

Generalised hyperbolic
Logarithmic
b = -1
T ->ln( 1 + D.x )
T'= D/( 1 + D.x )

Generalised hyperbolic
Integral
b < 0, b ≠ -1
T->(1 - (1 - b.D.x)((b + 1)/b))/(D.(b + 1))
T'->( 1 - b.D.x )(1/b)

Generalised hyperbolic
Harmonic
b = 1
T->1 - ( 1 + D.x )-1
T'-> D.( 1 + D.x )-2

Generalised hyperbolic
Hyperbolic
b > 0, b ≠ 1
T-> 1 - ( 1 + b.D.x )(-1/b)
T'->D.(1 + b.D.x)(-(1+b)/b)


Power law
T(x) = 1 - (1 - x)1 + D
T'(x) = (1 + D).(1 - x)D


The maximum gradient for the base equations occurs at x=0. This point defines the point of maximum intensity and we want that to occur at x = SP. So we transform the equation by defining:
T3(x) = T(x-SP).
This broadly defines the transformation for the range SP ≤ x < HP although it will need to be normalised as described later below.
The transformation for LP ≤ x < SP is defined by symmetry as follows:
T2(x) = -T(SP-x).
This, in effect, is equivalent to rotating the graph above SP through 180° around the point (SP, 0) - hence the name, Symmetry point.
For the range 0.0 ≤ x < LP we want a linear transformation so we calculate the gradient of T2 at LP, ie T2'(LP), where the prime represents the first derivative. We also want the line to pass through the point (LP, T2(LP)). So we define:
T1(x) = T2'(LP) * (x - LP) + T2(LP)
Similarly for the range HP ≤ x ≤ 1.0, we calculate a linear transformation as follows:
T4(x) = T3'(HP) * (x - HP) + T3(HP)
Finally we want the transformed values to run from 0.0 to 1.0 so we need to normalise. We define:
NormTi(x) = (Ti(x) - T1(0))/(T4(1) - T1(0)), for i = 1, 2, 3, 4

We then define the full transformation: NormT: x → NormT(x), as follows:
0 ≤ x < LP
NormT1(x)

LP ≤ x < SP
NormT2(x)

SP ≤ x < HP
NormT3(x)

HP ≤ x ≤ 1
NormT4(x)


Inverse transformation equations
Generalised hyperbolic
Exponential
b = 0
InvT(x) = -ln(1 - x)/D

Generalised hyperbolic
Logarithmic
b = -1
InvT(x) = (ex - 1)/D

Generalised hyperbolic
Integral
b < 0, b ≠ -1
InvT(x) = ((1 - (1 - (b+1).D.x)(b/(b+1)))/(D.b)

Generalised hyperbolic
Harmonic
b = 1
InvT(x) = /(D.(1 - x))

Generalised hyperbolic
Hyperbolic
b > 0, b ≠ 1
InvT(x) = ((1 - x)-b - 1)/(b.D)

Power law
InvT(x) = 1 - (1 - x)1/(1 + D)

Then we can define the full inverse transformation InvNormT: x -> InvNormT(x), as follows:

0 ≤ x < NormT(LP)
LP + (x' - T2(LP))/T2'(LP)

NormT(LP) ≤ x < NormT(SP)
SP - InvT(-x')

NormT(SP) ≤ x < NormT(HP)
SP + InvT(x')

NormT(HP) ≤ x ≤ 1
HP + (x' - T3(HP))/T3'(HP)

where
x' = T1(0) + x.(T4(1) - T1(0))
*/

/*
In a simplified way, an S-curve (or inverted S-curve) modifies the image.
The inflection point is defined by SP (Symmetry Point), for example 0.5 will generate a symmetrical 'S-curve' for RGB values ​​lower than SP or Higher.

Stretch factor will make this curve more or less pronounced with very gradual asymptotes in the low and high lights.

Linear factor will change the shape of the S, reducing or increasing the "length" of the asymptotic parts.

All 3 allow you to modify the contrast of the image by filling the valleys and reducing the peaks

*/

// end GHT Siril 

// From Siril.
ght_compute_params ImProcFunctions::GHT_setup(float in_B, float D, float LP, float SP, float HP, GHTStrType strtype)
{
    rtengine::ght_compute_params c;
    float B = in_B;
    if (strtype == GHTStrType::NORMAL) {//Normal Stretch
        if (B == -1.0f) {
            c.qlp = -1.0f * log(1.f + D * (SP - LP));
            c.q0 = c.qlp - D * LP / (1.0f + D * (SP - LP));
            c.qwp = log(1.f + D * (HP - SP));
            c.q1 = c.qwp + D * (1.0f - HP) / (1.0f + D * (HP - SP));
            c.q = 1.0f / (c.q1 - c.q0);
            c.b1 = (1.0f + D * (SP - LP)) / (D * c.q);
            c.a2 = (-c.q0) * c.q;
            c.b2 = -c.q;
            c.c2 = 1.0f + D * SP;
            c.d2 = -D;
            c.a3 = (-c.q0) * c.q;
            c.b3 = c.q;
            c.c3 = 1.0f - D * SP;
            c.d3 = D;
            c.a4 = (c.qwp - c.q0 - D * HP / (1.0f + D * (HP - SP))) * c.q;
            c.b4 = c.q * D / (1.0f + D * (HP - SP));
        } else if (B < 0.0f) {
            B = -B;
            c.qlp = (1.0f - pow((1.0f + D * B * (SP - LP)), (B - 1.0f) / B)) / (B - 1.0f);
            c.q0 = c.qlp - D * LP * (pow((1.0f + D * B * (SP - LP)), -1.0f / B));
            c.qwp = (pow((1.0f + D * B * (HP - SP)), (B - 1.0f) / B) - 1.0f) / (B - 1.0f);
            c.q1 = c.qwp + D * (1.0f - HP) * (pow((1.0f + D * B * (HP - SP)), -1.0f / B));
            c.q = 1.0f / (c.q1 - c.q0);
            c.b1 = D * pow(1.0f + D * B * (SP - LP), -1.0f / B) *c.q;
            c.a2 = (1.0f / (B - 1.0f) - c.q0) * c.q;
            c.b2 = -c.q / (B - 1.0f);
            c.c2 = 1.0f + D * B * SP;
            c.d2 = -D * B;
            c.e2 = (B - 1.0f) / B;
            c.a3 = (-1.0f / (B-1.0f) - c.q0) *c.q;
            c.b3 = c.q/(B-1.0f);
            c.c3 = 1.0f - D * B * SP;
            c.d3 = D * B;
            c.e3 = (B - 1.0f) / B;
            c.a4 = (c.qwp - c.q0 - D * HP * pow((1.0f + D * B * (HP - SP)), -1.0f / B)) * c.q;
            c.b4 = D * pow((1.0f + D * B * (HP - SP)), -1.0f / B) * c.q;
        } else if (B == 0.0f) {
            c.qlp = exp(-D * (SP - LP));
            c.q0 = c.qlp - D * LP * exp(-D*(SP - LP));
            c.qwp = 2.0f - exp(-D * (HP -SP));
            c.q1 = c.qwp + D * (1.0f - HP) * exp (-D * (HP - SP));
            c.q = 1.0f / (c.q1 - c.q0);
            c.a1 = 0.0f;
            c.b1 = D * exp (-D * (SP - LP)) * c.q;
            c.a2 = -c.q0 * c.q;
            c.b2 = c.q;
            c.c2 = -D * SP;
            c.d2 = D;
            c.a3 = (2.0f - c.q0) * c.q;
            c.b3 = -c.q;
            c.c3 = D * SP;
            c.d3 = -D;
            c.a4 = (c.qwp - c.q0 - D * HP * exp(-D * (HP - SP))) * c.q;
            c.b4 = D * exp(-D * (HP - SP)) * c.q;
        } else if (B > 0.0f) {
            c.qlp = pow((1.0f + D * B * (SP - LP)), -1.0f / B);
            c.q0 = c.qlp - D * LP * pow((1.f + D * B * (SP - LP)), -(1.0f + B) / B);
            c.qwp = 2.0f - pow(1.0f + D * B * (HP - SP), -1.0f / B);
            c.q1 = c.qwp + D * (1.0f - HP) * pow((1.0f + D * B * (HP - SP)), -(1.0f + B) / B);
            c.q = 1.0f / (c.q1 - c.q0);
            c.b1 = D * pow((1.0f + D * B * (SP - LP)), -(1.0f+B)/B) * c.q;
            c.a2 = -c.q0 * c.q;
            c.b2 = c.q;
            c.c2 = 1.0f + D * B * SP;
            c.d2 = -D * B;
            c.e2 = -1.0f / B;
            c.a3 = (2.0f - c.q0) * c.q;
            c.b3 = -c.q;
            c.c3 = 1.0f - D * B * SP;
            c.d3 = D * B;
            c.e3 = -1.0f / B;
            c.a4 = (c.qwp - c.q0 - D * HP * pow((1.0f + D * B * (HP - SP)), -(B + 1.0f) / B)) * c.q;
            c.b4 = (D * pow((1.0f + D * B * (HP - SP)), -(B + 1.0f) / B)) * c.q;
        }
    } else if (strtype == GHTStrType::INVERSE) {//Inverse stretch
        if (B == -1.0f) {
            c.qlp = -1.0f * log(1.f + D * (SP - LP));
            c.q0 = c.qlp - D * LP / (1.0f + D * (SP - LP));
            c.qwp = log(1.f + D * (HP - SP));
            c.q1 = c.qwp + D * (1.0f - HP) / (1.0f + D * (HP - SP));
            c.q = 1.0f / (c.q1 - c.q0);
            c.LPT = (c.qlp-c.q0)*c.q;
            c.SPT = c.q0*c.q;
            c.HPT = (c.qwp-c.q0)*c.q;
            c.b1 = (1.0f + D * (SP - LP)) / (D * c.q);
            c.a2 = (1.0f + D * SP) / D;
            c.b2 = -1.0f / D;
            c.c2 = - c.q0;
            c.d2 = - 1.0f/c.q;
            c.a3 = - (1.0f - D * SP) / D;
            c.b3 = 1.0f / D;
            c.c3 = c.q0;
            c.d3 = 1.0f / c.q;
            c.a4 = HP + (c.q0 - c.qwp) * (1.f + D * (HP-SP)) / D;
            c.b4 = (1.0f + D * (HP - SP) )/(c.q * D) ;
        } else if (B < 0.0f) {
           B = -B;
            c.qlp = (1.0f - pow((1.0f + D * B * (SP - LP)), (B - 1.0f) / B)) / (B - 1.0f);
            c.q0 = c.qlp - D * LP * (pow((1.0f + D * B * (SP - LP)), -1.0f / B));
            c.qwp = (pow((1.0f + D * B * (HP - SP)), (B - 1.0f) / B) - 1.0f) / (B - 1.0f);
            c.q1 = c.qwp + D * (1.0f - HP) * (pow((1.0f + D * B * (HP - SP)), -1.0f / B));
            c.q = 1.0f / (c.q1 - c.q0);
            c.LPT = (c.qlp - c.q0)*c.q;
            c.SPT = -c.q0 * c.q;
            c.HPT = (c.qwp - c.q0) * c.q;
            c.b1 = pow(1.0f + D * B * (SP - LP), 1.0f / B) / (c.q * D);
            c.a2 = (1.0f + D * B * SP) / (D * B);
            c.b2 = -1.0f / (D * B);
            c.c2 = -c.q0 * (B-1.0f) + 1.0f;
            c.d2 = (1.0f - B) / c.q;
            c.e2 = B / (B - 1.0f);
            c.a3 = (D * B * SP - 1.0f) / (D * B);
            c.b3 = 1.0f / (D * B);
            c.c3 = 1.0f + c.q0 * (B - 1);
            c.d3 = (B - 1.0f) / c.q;
            c.e3 = B / (B - 1.0f);
            c.a4 = (c.q0 - c.qwp)/(D * pow((1.0f + D * B * (HP - SP)), -1.0f / B)) + HP;
            c.b4 = 1.0f / (D * pow((1.0f + D * B * (HP - SP)), -1.0f / B) * c.q) ;
        } else if (B == 0.0f) {
            c.qlp = exp(-D * (SP - LP));
            c.q0 = c.qlp - D * LP * exp(-D*(SP - LP));
            c.qwp = 2.0f - exp(-D * (HP -SP));
            c.q1 = c.qwp + D * (1.0f - HP) * exp (-D * (HP - SP));
            c.q = 1.0f / (c.q1 - c.q0);
            c.LPT = (c.qlp - c.q0) * c.q;
            c.SPT = (1.0f - c.q0) * c.q;
            c.HPT = (c.qwp - c.q0) * c.q;
            c.a1 = 0.0f;
            c.b1 = 1.0f / (D * exp(-D * (SP - LP)) * c.q);
            c.a2 = SP;
            c.b2 = 1.0f / D;
            c.c2 = c.q0;
            c.d2 = 1.0f / c.q;
            c.a3 = SP;
            c.b3 = -1.0f / D;
            c.c3 = (2.0f - c.q0);
            c.d3 = -1.0f / c.q;
            c.a4 = (c.q0 - c.qwp)/(D * exp(-D * (HP - SP))) + HP;
            c.b4 = 1.0f / (D * exp(-D * (HP - SP)) * c.q);
        } else if (B > 0.0f) {
            c.qlp = pow(( 1.0f + D * B * (SP - LP)), -1.0f/B);
            c.q0 = c.qlp - D * LP * pow((1.0f + D * B * (SP - LP)), -(1.0f + B) / B);
            c.qwp = 2.0f - pow(1.0f + D * B * (HP - SP), -1.0f / B);
            c.q1 = c.qwp + D * (1.0f - HP) * pow((1.0f + D * B * (HP - SP)), -(1.0f + B) / B);
            c.q = 1.0f / (c.q1 - c.q0);
            c.LPT = (c.qlp - c.q0) * c.q;
            c.SPT = (1.0f - c.q0) * c.q;
            c.HPT = (c.qwp - c.q0) * c.q;
            c.b1 = 1.f / (D * pow((1.0f + D * B * (SP - LP)), -(1.0f + B) / B) * c.q);
            c.a2 = 1.0f / (D * B) + SP;
            c.b2 = -1.0f / (D * B);
            c.c2 = c.q0;
            c.d2 = 1.0f / c.q;
            c.e2 = -B;
            c.a3 = -1.0f / (D * B) + SP;
            c.b3 = 1.0f / (D * B);
            c.c3 = (2.0f - c.q0);
            c.d3 = -1.0f / c.q;
            c.e3 = -B;
            c.a4 = (c.q0 - c.qwp)/(D * pow((1.0f + D * B * (HP - SP)), -(B + 1.0f) / B)) + HP;
            c.b4 = 1.0f/((D * pow((1.0f + D * B * (HP - SP)), -(B + 1.0f) / B)) * c.q);
        }
    }
    return c;
}

// From Siril.
float ImProcFunctions::GHT(float x, float B, float D, float LP, float SP, float HP, rtengine::ght_compute_params c, GHTStrType strtype)
{
    float out;
    float in = clamp(x, 0.f, 1.f);//never negatives values or > 1. hence the need to control the Black point and White point
    if (D == 0.0f) {//no stretch
        out = in;
    } else {
        if (strtype == GHTStrType::NORMAL) {//Normal stretch
            if (B == -1.0f) {
                if (in < LP) {
                    out = c.b1 * in;
                } else if (in < SP) {
                    out = c.a2 + c.b2 * log(c.c2 + c.d2 * in);
                } else if (in < HP) {
                    out = c.a3 + c.b3 * log(c.c3 + c.d3 * in);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else if (B < 0.0f) {
                if (in < LP) {
                    out = c.b1 * in;
                } else if (in < SP) {
                    out = c.a2 + c.b2 * pow((c.c2 + c.d2 * in), c.e2);
                } else if (in < HP) {
                    out = c.a3 + c.b3 * pow((c.c3 + c.d3 * in), c.e3);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else if (B == 0.0f) {
                if (in < LP) {
                    out = c.a1 + c.b1 * in;
                } else if (in < SP) {
                    out = c.a2 + c.b2 * exp(c.c2 + c.d2 * in);
                } else if (in < HP) {
                    out = c.a3 + c.b3 * exp(c.c3 + c.d3 * in);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else /*if (B > 0)*/ {
                if (in < LP) {
                    out = c.b1 * in;
                } else if (in < SP) {
                    out = c.a2 + c.b2 * pow((c.c2 + c.d2 * in), c.e2);
                } else if (in < HP) {
                    out = c.a3 + c.b3 * pow((c.c3 + c.d3 * in), c.e3);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            }
        } if (strtype == GHTStrType::INVERSE) {//Inverse Stretch
            if (B == -1.0f) {
                if (in < c.LPT) {
                    out = c.b1 * in;
                } else if (in < c.SPT) {
                    out = c.a2 + c.b2 * exp(c.c2 + c.d2 * in);
                } else if (in < c.HPT) {
                    out = c.a3 + c.b3 * exp(c.c3 + c.d3 * in);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else if (B < 0.0f) {
                if (in < c.LPT) {
                    out = c.b1 * in;
                } else if (in < c.SPT) {
                    out = c.a2 + c.b2 * pow((c.c2 + c.d2 * in), c.e2);
                } else if (in < c.HPT) {
                    out = c.a3 + c.b3 * pow((c.c3 + c.d3 * in), c.e3);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else if (B == 0.0f) {
                if (in < c.LPT) {
                    out = c.a1 + c.b1 * in;
                } else if (in < c.SPT) {
                    out = c.a2 + c.b2 * logf(c.c2 + c.d2 * in);
                } else if (in < c.HPT) {
                    out = c.a3 + c.b3 * logf(c.c3 + c.d3 * in);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            } else /* if (B > 0) */{
                if (in < c.LPT) {
                    out = c.b1 * in;
                } else if (in < c.SPT) {
                    out = c.a2 + c.b2 * pow((c.c2 + c.d2 * in), c.e2);
                } else if (in < c.HPT) {
                    out = c.a3 + c.b3 * pow((c.c3 + c.d3 * in), c.e3);
                } else {
                    out = c.a4 + c.b4 * in;
                }
            }
        }
    }
    return out;
}
/*
void SymRgb(const float * DataList, const int datalen)
{
    if (datalen <= 1) { // Avoid possible buffer underrun
        return 0;
    }

    //DataList values should mostly have abs val < 65536 because we are in RGB mode
    int * histo = new int[65536];

    for (int i = 0; i < 65536; ++i) {
        histo[i] = 0;
    }

    //calculate histogram of absolute values of wavelet coeffs
    for (int i = 0; i < datalen; ++i) {
        histo[static_cast<int>(rtengine::min(65535.f, fabsf(DataList[i])))]++;
    }
    int maxhist = 0;
    for (int j = 0; j < 65535; ++j) {
        if(histo[j] > maxhist) {
            maxhist = histo[j]
        }       
    }
    printf("maxhist=%i \n", maxhist);

}
*/




void ImProcFunctions::Lab_Local(
    int call, int sp, float** shbuffer, LabImage * original, LabImage * transformed, LabImage * reserved, LabImage * savenormtm, LabImage * savenormreti, LabImage * lastorig, int fw, int fh, int cx, int cy, int oW, int oH, int tX, int tY, int tW, int tH, int sk,
    const LocretigainCurve& locRETgainCcurve, const LocretitransCurve& locRETtransCcurve,
    const LUTf& lllocalcurve, bool locallutili,
    const LUTf& cllocalcurve, bool localclutili,
    const LUTf& lclocalcurve, bool locallcutili,
    const LocLHCurve& loclhCurve,  const LocHHCurve& lochhCurve, const LocCHCurve& locchCurve,
    const LocHHCurve& lochhCurvejz, const LocCHCurve& locchCurvejz, const LocLHCurve& loclhCurvejz,
    const LUTf& lmasklocalcurve, bool localmaskutili,
    const LUTf& lmaskexplocalcurve, bool localmaskexputili,
    const LUTf& lmaskSHlocalcurve, bool localmaskSHutili,
//    const LUTf& ghslocalcurve, bool localghsutili,
    const LUTf& lmaskviblocalcurve, bool localmaskvibutili,
    const LUTf& lmasktmlocalcurve, bool localmasktmutili,
    LUTf& lmaskretilocalcurve, bool localmaskretiutili,
    const LUTf& lmaskcblocalcurve, bool localmaskcbutili,
    const LUTf& lmaskbllocalcurve, bool localmaskblutili,
    const LUTf& lmasklclocalcurve, bool localmasklcutili,
    const LUTf& lmaskloglocalcurve, bool localmasklogutili,
    const LUTf& lmasklocal_curve, bool localmask_utili,
    const LUTf& lmaskcielocalcurve, bool localmaskcieutili,
    const LUTf& cielocalcurve, bool localcieutili,
    const LUTf& cielocalcurve2, bool localcieutili2,
    const LUTf& jzlocalcurve, bool localjzutili,
    const LUTf& czlocalcurve, bool localczutili,
    const LUTf& czjzlocalcurve, bool localczjzutili,

    const LocCCmaskCurve& locccmasCurve, bool lcmasutili, const LocLLmaskCurve& locllmasCurve, bool llmasutili, const LocHHmaskCurve& lochhmasCurve, bool lhmasutili, const LocHHmaskCurve& llochhhmasCurve, bool lhhmasutili, const LocHHmaskCurve& llochhhmascieCurve, bool lhhmascieutili,
    const LocCCmaskCurve& locccmasexpCurve, bool lcmasexputili, const LocLLmaskCurve& locllmasexpCurve, bool llmasexputili, const LocHHmaskCurve& lochhmasexpCurve, bool lhmasexputili,
    const LocCCmaskCurve& locccmasSHCurve, bool lcmasSHutili, const LocLLmaskCurve& locllmasSHCurve, bool llmasSHutili, const LocHHmaskCurve& lochhmasSHCurve, bool lhmasSHutili,
    const LocCCmaskCurve& locccmasvibCurve, bool lcmasvibutili, const LocLLmaskCurve& locllmasvibCurve, bool llmasvibutili, const LocHHmaskCurve& lochhmasvibCurve, bool lhmasvibutili,
    const LocCCmaskCurve& locccmascbCurve, bool lcmascbutili, const LocLLmaskCurve& locllmascbCurve, bool llmascbutili, const LocHHmaskCurve& lochhmascbCurve, bool lhmascbutili,
    const LocCCmaskCurve& locccmasretiCurve, bool lcmasretiutili, const LocLLmaskCurve& locllmasretiCurve, bool llmasretiutili, const LocHHmaskCurve& lochhmasretiCurve, bool lhmasretiutili,
    const LocCCmaskCurve& locccmastmCurve, bool lcmastmutili, const LocLLmaskCurve& locllmastmCurve, bool llmastmutili, const LocHHmaskCurve& lochhmastmCurve, bool lhmastmutili,
    const LocCCmaskCurve& locccmasblCurve, bool lcmasblutili, const LocLLmaskCurve& locllmasblCurve, bool llmasblutili, const LocHHmaskCurve& lochhmasblCurve, bool lhmasblutili,
    const LocCCmaskCurve& locccmaslcCurve, bool lcmaslcutili, const LocLLmaskCurve& locllmaslcCurve, bool llmaslcutili, const LocHHmaskCurve& lochhmaslcCurve, bool lhmaslcutili,
    const LocCCmaskCurve& locccmaslogCurve, bool lcmaslogutili, const LocLLmaskCurve& locllmaslogCurve, bool llmaslogutili, const LocHHmaskCurve& lochhmaslogCurve, bool lhmaslogutili,
    const LocCCmaskCurve& locccmas_Curve, bool lcmas_utili, const LocLLmaskCurve& locllmas_Curve, bool llmas_utili, const LocHHmaskCurve& lochhmas_Curve, bool lhmas_utili,
    const LocCCmaskCurve& locccmascieCurve, bool lcmascieutili, const LocLLmaskCurve& locllmascieCurve, bool llmascieutili, const LocHHmaskCurve& lochhmascieCurve, bool lhmascieutili,

    const LocHHmaskCurve& lochhhmas_Curve, bool lhhmas_utili,
    const LocwavCurve& loclmasCurveblwav, bool lmasutiliblwav,
    const LocwavCurve& loclmasCurvecolwav, bool lmasutilicolwav,
    const LocwavCurve& loclmasCurveciewav, bool lmasutiliciewav,
    const LocwavCurve& locwavCurve, bool locwavutili,
    const LocwavCurve& locwavCurvejz, bool locwavutilijz,
    const LocwavCurve& loclevwavCurve, bool loclevwavutili,
    const LocwavCurve& locconwavCurve, bool locconwavutili,
    const LocwavCurve& loccompwavCurve, bool loccompwavutili,
    const LocwavCurve& loccomprewavCurve, bool loccomprewavutili,
    const LocwavCurve& locwavCurvehue, bool locwavhueutili,
    const LocwavCurve& locwavCurvehuecont, bool locwavhueutilicont,
    const LocwavCurve& locwavCurveden, bool locwavdenutili,
    const LocwavCurve& locedgwavCurve, bool locedgwavutili,
    const LocwavCurve& loclmasCurve_wav, bool lmasutili_wav,

    bool LHutili, bool HHutili, bool CHutili, bool HHutilijz, bool CHutilijz, bool LHutilijz, const LUTf& cclocalcurve, bool localcutili, const LUTf& rgblocalcurve, bool localrgbutili, bool localexutili, const LUTf& exlocalcurve, const LUTf& hltonecurveloc, const LUTf& shtonecurveloc, const LUTf& tonecurveloc, const LUTf& lightCurveloc,
    double& huerefblur, double& chromarefblur, double& lumarefblur, double& hueref, double& chromaref, double& lumaref, double& sobelref, int &lastsav,
    bool prevDeltaE, int llColorMask, int llColorMaskinv, int llExpMask, int llExpMaskinv, int llSHMask, int llSHMaskinv, int llvibMask, int lllcMask, int llsharMask, int llcbMask, int llretiMask, int llsoftMask, int lltmMask, int llblMask, int lllogMask, int ll_Mask, int llcieMask,
    float& minCD, float& maxCD, float& mini, float& maxi, float& Tmean, float& Tsigma, float& Tmin, float& Tmax,
    float& meantm, float& stdtm, float& meanreti, float& stdreti, float &fab,float &maxicam, float &rdx, float &rdy, float &grx, float &gry, float &blx, float &bly, float &meanx, float &meany, float &meanxe, float &meanye, float &maxdat,  int &prim, int &ill, float &contsig, float &lightsig, float &slopeg, bool &linkrgb,
    float *resi, float &sharc, float &denocont, int *ghsbpwp, float *ghsbpwpvalue, float *savmadl, float *ghsbwslider, float &ghssym, bool &ghsautsp,  float *ghscolor, float &ghsmid, float &ghsmaxrgb, float &ghs3sig, float *michbwslider)
    //michbwslider: added to facilitate a possible modification requested by users, but is not currently in use
{
    //general call of others functions : important return hueref, chromaref, lumaref
    if (!params->locallab.enabled) {
        return;
    }

    MyTime t1, t2;
    
    constexpr int del = 3; // to avoid crash with [loy - begy] and [lox - begx] and bfh bfw  // with gtk2 [loy - begy-1] [lox - begx -1 ] and del = 1
    struct local_params lp;
    calcLocalParams(sp, oW, oH, params->locallab, lp, prevDeltaE, llColorMask, llColorMaskinv, llExpMask, llExpMaskinv, llSHMask, llSHMaskinv, llvibMask, lllcMask, llsharMask, llcbMask, llretiMask, llsoftMask, lltmMask, llblMask, lllogMask, ll_Mask, llcieMask, locwavCurveden, locwavdenutili);
    
    //parameters to change behavior GF
    float ksk = 1.f;//acts on cy
    float kskx = 1.f;//acts on cx
    float kbh = 1.f;//acts on bkh in calcGradientFactor
    float kbw = 1.f;//acts on bfw in calcGradientFactor
    //2 others parameters in calclocalGradientParams for GF center and GF strength
        // kstop - strength GF
        // kx, ky acts on center GF.
    
    
    //BENCHFUN




    //Pre-filter zero and negative values RGB then Lab when using before SE CBDL or Dehaze, or processor type...

    int bw0 = transformed->W;
    int bh0 = transformed->H;

    float epsi0 = 0.000001f;
    bool nocrash = false;
    bool cbdl = false;
    if(params->dirpyrequalizer.cbdlMethod == "bef") {//If user choose "after black and white" this function which removes negative values is not used, hence CBDL is best performed, after Selective Editing in Lab mode
        cbdl = true;
    }
    nocrash = (params->dirpyrequalizer.enabled && cbdl)  ||  params->dehaze.enabled  || lp.avoidneg;//lp.avoidneg in setting 
    
    
    if(nocrash) {//allows memory and conversion labrgb only in these cases and prevent negative RGB values
        const std::unique_ptr<Imagefloat> prov0(new Imagefloat(bw0, bh0));
        lab2rgb(*transformed, *prov0, params->icm.workingProfile);
#ifdef _OPENMP
        #pragma omp parallel for
#endif
            for (int i = 0; i < bh0; ++i)
                for (int j = 0; j < bw0; ++j) {
                    prov0->r(i, j) = (rtengine::max(prov0->r(i, j), epsi0));
                    prov0->g(i, j) = (rtengine::max(prov0->g(i, j), epsi0));
                    prov0->b(i, j) = (rtengine::max(prov0->b(i, j), epsi0)); 
                }
        rgb2lab(*prov0, *transformed, params->icm.workingProfile);
    }

    const float radius = lp.rad / (sk * 1.4); //0 to 70 ==> see skip
    int levred;
    bool noiscfactiv;

    if (lp.qualmet == 2) { //suppress artifacts with quality enhanced
        levred = 4;
        noiscfactiv = true;
    } else {
        levred = 7;
        noiscfactiv = false;
    }

//lastsav for save restore image
    lastsav = 0;

    if (lp.excmet == 1 && call <= 3 && lp.activspot) {//exclude
        const int bfh = int (lp.ly + lp.lyT) + del; //bfw bfh real size of square zone
        const int bfw = int (lp.lx + lp.lxL) + del;
        const int begy = lp.yc - lp.lyT;
        const int begx = lp.xc - lp.lxL;
        const int yEn = lp.yc + lp.ly;
        const int xEn = lp.xc + lp.lx;
        LabImage bufreserv(bfw, bfh);
        array2D<float> bufsob(bfw, bfh);

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

        for (int y = rtengine::max(begy - cy, 0); y < rtengine::min(yEn - cy, original->H); y++) {
            const int loy = cy + y;

            for (int x = rtengine::max(begx - cx, 0); x < rtengine::min(xEn - cx, original->W); x++) {
                const int lox = cx + x;

                bufsob[loy - begy][lox - begx] = bufreserv.L[loy - begy][lox - begx] = reserved->L[y][x];
                bufreserv.a[loy - begy][lox - begx] = reserved->a[y][x];
                bufreserv.b[loy - begy][lox - begx] = reserved->b[y][x];
            }
        }

        array2D<float> ble(bfw, bfh);
        const float radiussob = 1.f / (sk * 1.4f);
        SobelCannyLuma(ble, bufsob, bfw, bfh, radiussob);
        array2D<float> &guid = bufsob;

#ifdef _OPENMP
        #pragma omp parallel for if(multiThread)
#endif

        for (int ir = 0; ir < bfh; ir++)
            for (int jr = 0; jr < bfw; jr++) {
                ble[ir][jr] /= 32768.f;
                guid[ir][jr] /= 32768.f;
            }


        const float blur = 25 / sk * (2.f + 2.5f * lp.struexp);

        rtengine::guidedFilter(guid, ble, ble, blur, 0.0001, multiThread);

//        const float blur = 25 / sk * (10.f + 0.8f * lp.struexp);

//        rtengine::guidedFilter(guid, ble, ble, blur, 0.001, multiThread);

        double sombel = 0.f;
        const int ncsobel = bfh * bfw;

        array2D<float> &deltasobelL = guid;

#ifdef _OPENMP
        #pragma omp parallel for reduction(+:sombel) if(multiThread)
#endif

        for (int ir = 0; ir < bfh; ir++) {
            for (int jr = 0; jr < bfw; jr++) {
                const float val = ble[ir][jr] * 32768.f;
                sombel += static_cast<double>(val);
                deltasobelL[ir][jr] = val;
            }
        }

        const float meansob = sombel / ncsobel;
        Exclude_Local(deltasobelL, hueref, chromaref, lumaref, sobelref, meansob, lp, original, transformed, &bufreserv, reserved, cx, cy, sk);
    }

//encoding lab at the beginning
    if (lp.logena && (call <= 3 || lp.prevdE || lp.showmasklogmet == 2 || lp.enaLMask || lp.showmasklogmet == 3 || lp.showmasklogmet == 4)) {

        const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        const int bfh = yend - ystart;
        const int bfw = xend - xstart;

        if (bfh >= mSP && bfw >= mSP) {
            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh)); //buffer for data in zone limit
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh)); //buffer for data in zone limit

            std::unique_ptr<LabImage> bufmaskblurlog;
            std::unique_ptr<LabImage> originalmasklog;
            std::unique_ptr<LabImage> bufmaskoriglog;

            if (lp.showmasklogmet == 2  || lp.enaLMask || lp.showmasklogmet == 3 || lp.showmasklogmet == 4) {
                bufmaskblurlog.reset(new LabImage(bfw, bfh));
                originalmasklog.reset(new LabImage(bfw, bfh));
                bufmaskoriglog.reset(new LabImage(bfw, bfh));
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

            for (int y = ystart; y < yend; y++) {
                for (int x = xstart; x < xend; x++) {
                    bufexporig->L[y - ystart][x - xstart] = original->L[y][x];
                    bufexporig->a[y - ystart][x - xstart] = original->a[y][x];
                    bufexporig->b[y - ystart][x - xstart] = original->b[y][x];
                }
            }

            int inv = 0;
            bool showmaske = false;
            bool enaMask = false;
            bool deltaE = false;
            bool modmask = false;
            bool zero = false;
            bool modif = false;

            if (lp.showmasklogmet == 3) {
                showmaske = true;
            }

            if (lp.enaLMask) {
                enaMask = true;
            }

            if (lp.showmasklogmet == 4) {
                deltaE = true;
            }

            if (lp.showmasklogmet == 2) {
                modmask = true;
            }

            if (lp.showmasklogmet == 1) {
                modif = true;
            }

            if (lp.showmasklogmet == 0) {
                zero = true;
            }

            float chrom = lp.chromaL;
            float rad = lp.radmaL;
            float blendm = lp.blendmaL;
            float gamma = 1.f;
            float slope = 0.f;
            float lap = 0.f; //params->locallab.spots.at(sp).lapmaskexp;
            bool pde = false; //params->locallab.spots.at(sp).laplac;
            LocwavCurve dummy;
            bool delt = params->locallab.spots.at(sp).deltae;
            int sco = params->locallab.spots.at(sp).scopemask;
            int shado = 0;
            int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;
            const float mindE = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            float amountcd = 0.f;
            float anchorcd = 50.f;
            int lumask = params->locallab.spots.at(sp).lumask;
            LocHHmaskCurve lochhhmasCurve;
            const int highl = 0;
            maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufexporig.get(), bufmaskoriglog.get(), originalmasklog.get(), original, reserved, inv, lp,
                        0.f, false,
                        locccmaslogCurve, lcmaslogutili, locllmaslogCurve, llmaslogutili, lochhmaslogCurve, lhmaslogutili, lochhhmasCurve, false, multiThread,
                        enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskloglocalcurve, localmasklogutili, dummy, false, 1, 1, 5, 5,
                        shortcu, delt, hueref, chromaref, lumaref,
                        maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                       );

            if (lp.showmasklogmet == 3) {
                showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufexporig.get(), transformed, bufmaskoriglog.get(), 0);

                return;
            }

            if (lp.showmasklogmet == 0 || lp.showmasklogmet == 1  || lp.showmasklogmet == 2 || lp.showmasklogmet == 4 || lp.enaLMask) {

                bufexpfin->CopyFrom(bufexporig.get(), multiThread);
                std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));
                std::unique_ptr<Imagefloat> tmpImageorig(new Imagefloat(bfw, bfh));
                lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile);
#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                for (int y = 0; y < bfh; y++) {
                    for (int x = 0; x < bfw; x++) {
                        tmpImageorig->r(y, x) = tmpImage->r(y, x);
                        tmpImageorig->g(y, x) = tmpImage->g(y, x);
                        tmpImageorig->b(y, x) = tmpImage->b(y, x);
                    }
                }

                log_encode(tmpImage.get(), lp, multiThread, bfw, bfh);
                
                
                const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).repar;

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                for (int y = 0; y < bfh; y++) {
                    for (int x = 0; x < bfw; x++) {
                        tmpImage->r(y, x) = intp(repart, tmpImageorig->r(y, x), tmpImage->r(y, x));
                        tmpImage->g(y, x) = intp(repart, tmpImageorig->g(y, x), tmpImage->g(y, x));
                        tmpImage->b(y, x) = intp(repart, tmpImageorig->b(y, x), tmpImage->b(y, x));
                    }
                }

                rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile);

                tmpImageorig.reset();
                tmpImage.reset();

                if (params->locallab.spots.at(sp).ciecam) {
                    bool HHcurvejz = false, CHcurvejz = false, LHcurvejz = false;
                    ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 1, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                }


                if (params->locallab.spots.at(sp).expcie && params->locallab.spots.at(sp).modecie == "log") {
                    bool HHcurvejz = false;
                    bool CHcurvejz = false;
                    bool LHcurvejz = false;

                    if (params->locallab.spots.at(sp).expcie  && params->locallab.spots.at(sp).modecam == "jz") {//some cam16 elementsfor Jz
                        ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 10, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                    }

                    ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 0, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);

                    float rad = params->locallab.spots.at(sp).detailcie;
                    loccont(bfw, bfh, bufexpfin.get(), rad, 15.f, sk);
                }

                //here begin graduated filter
                //first solution "easy" but we can do other with log_encode...to see the results
                if (lp.strlog != 0.f) {
                    struct grad_params gplog;
                    calclocalGradientParams(call, lp, gplog, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 11, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif
                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufexpfin->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gplog, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                        }
                    }
                }

                //end graduated

                float recoth = lp.recothrl;

                if (lp.recothrl < 1.f) {
                    recoth = -1.f * recoth + 2.f;
                }

                if (lp.enaLMask && lp.recothrl != 1.f) {
                    float hig = lp.higthrl;
                    float low = lp.lowthrl;
                    // float recoth = lp.recothrl;
                    float decay = lp.decayl;
                    bool invmask = false;
                    maskrecov(bufexpfin.get(), original, bufmaskoriglog.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                }

                if (lp.recothrl >= 1.f) {
                    transit_shapedetect2(sp, 0.f, 0.f, call, 11, bufexporig.get(), bufexpfin.get(), originalmasklog.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD,  cx, cy, sk);
                } else {
                    transit_shapedetect2(sp, 0.f, 0.f, call, 11, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                }
            }

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }

//Prepare mask for Blur and noise and Denoise
    bool denoiz = false;

    if ((lp.noiself > 0.f || lp.noiself0 > 0.f || lp.noiself2 > 0.f || lp.noiselc > 0.f || lp.wavcurvedenoi || lp.nlstr > 0 || lp.noisecf > 0.f || lp.noisecc > 0.f  || lp.bilat > 0.f) && lp.denoiena) {
        denoiz = true;
    }

    bool blurz = false;
    bool delt = params->locallab.spots.at(sp).deltae;

    if (((static_cast<double>(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 TW = transformed->W;
    const int TH = transformed->H;
    const std::unique_ptr<LabImage> bufblorig(new LabImage(TW, TH));

    std::unique_ptr<LabImage> originalmaskbl;
    std::unique_ptr<LabImage> bufmaskorigbl;
    std::unique_ptr<LabImage> bufmaskblurbl;
    std::unique_ptr<LabImage> bufprov(new LabImage(TW, TH));

    if (denoiz || blurz || lp.denoiena || lp.blurena) {
        if (lp.showmaskblmet == 2  || lp.enablMask || lp.showmaskblmet == 3 || lp.showmaskblmet == 4) {
            bufmaskorigbl.reset(new LabImage(TW, TH));
            bufmaskblurbl.reset(new LabImage(TW, TH, true));
            originalmaskbl.reset(new LabImage(TW, TH));
        }

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < TH; y++) {
            for (int x = 0; x < TW; x++) {
                bufblorig->L[y][x] = original->L[y][x];
            }
        }

        int inv = 0;
        bool showmaske = false;
        bool enaMask = false;
        bool deltaE = false;
        bool modmask = false;
        bool zero = false;
        bool modif = false;

        if (lp.showmaskblmet == 3) {
            showmaske = true;
        }

        if (lp.enablMask) {
            enaMask = true;
        }

        if (lp.showmaskblmet == 4) {
            deltaE = true;
        }

        if (lp.showmaskblmet == 2) {
            modmask = true;
        }

        if (lp.showmaskblmet == 1) {
            modif = true;
        }

        if (lp.showmaskblmet == 0) {
            zero = true;
        }

        float chrom = lp.chromabl;
        float rad = lp.radmabl;
        float gamma = lp.gammabl;
        float slope = lp.slomabl;
        float blendm = lp.blendmabl;
        float lap = params->locallab.spots.at(sp).lapmaskbl;
        bool pde = params->locallab.spots.at(sp).laplac;
        LocwavCurve dummy;
        int lumask = params->locallab.spots.at(sp).lumask;
        int sco = params->locallab.spots.at(sp).scopemask;
        int shortcu = 0;

        const float mindE = 2.f + MINSCOPE * sco * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
        const int shado = params->locallab.spots.at(sp).shadmaskblsha;
        const int highl = params->locallab.spots.at(sp).shadmaskbl;
        constexpr float amountcd = 0.f;
        constexpr float anchorcd = 50.f;
        LocHHmaskCurve lochhhmasCurve;
        const float strumask = 0.02 * params->locallab.spots.at(sp).strumaskbl;
        const bool astool = params->locallab.spots.at(sp).toolbl;
        maskcalccol(call, false, pde, TW, TH, oW, oH, tX, tY, tW, tH, 0, 0, 0, 0, sk, cx, cy, bufblorig.get(), bufmaskblurbl.get(), originalmaskbl.get(), original, reserved, inv, lp,
                    strumask, astool, locccmasblCurve, lcmasblutili, locllmasblCurve, llmasblutili, lochhmasblCurve, lhmasblutili, lochhhmasCurve, false, multiThread,
                    enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskbllocalcurve,
                    localmaskblutili, loclmasCurveblwav, lmasutiliblwav, 1, 1, 5, 5, shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                    maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, 0, fab, fw, fh, ksk, kskx, kbh, kbw
                   );

        if (lp.showmaskblmet == 3) {
            showmask(lumask, lp, 0, 0, cx, cy, TW, TH, bufblorig.get(), transformed, bufmaskblurbl.get(), inv);
            return;
        }

    }

    bool execmaskblur = (lp.showmaskblmet == 2 || lp.enablMask || lp.showmaskblmet == 3 || lp.showmaskblmet == 4) && lp.smasktyp != 1;
    int strengr = params->locallab.spots.at(sp).strengr;

    if (((static_cast<double>(radius) > 1.5 * GAUSS_SKIP && lp.rad > 1.6) || lp.stren > 0.1 || lp.blmet == 1 || lp.guidb > 0 || strengr > 0 || execmaskblur) && lp.blurena) { // radius < GAUSS_SKIP means no gauss, just copy of original image
        std::unique_ptr<LabImage> tmp1;
        std::unique_ptr<LabImage> tmp2;
        std::unique_ptr<LabImage> tmp3;
        std::unique_ptr<LabImage> maskk;
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;
        int bfhr = bfh;
        int bfwr = bfw;

        bool fft = params->locallab.spots.at(sp).fftwbl;
        int isogr = params->locallab.spots.at(sp).isogr;
        int scalegr = params->locallab.spots.at(sp).scalegr;
        float  divgr = params->locallab.spots.at(sp).divgr;


        if (bfw >= mSP && bfh >= mSP) {
            if (lp.blurmet == 0 && (fft || lp.rad > 30.0)) {
                optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            const std::unique_ptr<LabImage> bufgbi(new LabImage(TW, TH));

            //here mask is used with plain image for normal and inverse
            //if it is possible to optimize with maskcalccol(), I don't to preserve visibility
            if (lp.showmaskblmet == 0 || lp.showmaskblmet == 1  || lp.showmaskblmet == 2 || lp.showmaskblmet == 4 || lp.enablMask) {

                if (lp.blurmet == 0) {
                    if (bfw > 0 && bfh > 0) {
                        tmp1.reset(new LabImage(bfw, bfh));
                        tmp3.reset(new LabImage(bfw, bfh));
                        maskk.reset(new LabImage(bfw, bfh));
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = ystart; y < yend ; y++) {
                            for (int x = xstart; x < xend; x++) {
                                tmp1->L[y - ystart][x - xstart] = original->L[y][x];
                                tmp1->a[y - ystart][x - xstart] = original->a[y][x];
                                tmp1->b[y - ystart][x - xstart] = original->b[y][x];
                                tmp3->L[y - ystart][x - xstart] = original->L[y][x];//tmp3 needs for recovery mask
                                tmp3->a[y - ystart][x - xstart] = original->a[y][x];
                                tmp3->b[y - ystart][x - xstart] = original->b[y][x]; 
                            }
                        }
                    }
                } else if (lp.blurmet == 1) {
                    tmp1.reset(new LabImage(transformed->W, transformed->H));
                    tmp2.reset(new LabImage(transformed->W, transformed->H));
                    tmp3.reset(new LabImage(transformed->W, transformed->H));

                    for (int y = 0; y < TH ; y++) {
                        for (int x = 0; x < TW; x++) {
                            tmp2->L[y][x] = original->L[y][x];
                            tmp2->a[y][x] = original->a[y][x];
                            tmp2->b[y][x] = original->b[y][x];
                            tmp3->L[y][x] = original->L[y][x];
                            tmp3->a[y][x] = original->a[y][x];
                            tmp3->b[y][x] = original->b[y][x];
                            tmp1->L[y][x] = original->L[y][x];
                            tmp1->a[y][x] = original->a[y][x];
                            tmp1->b[y][x] = original->b[y][x];
                            bufgbi->L[y][x] = original->L[y][x];
                            bufgbi->a[y][x] = original->a[y][x];
                            bufgbi->b[y][x] = original->b[y][x];
                        }
                    }

                }


                if (lp.blurmet == 0 && lp.blmet == 0 && static_cast<double>(radius) > (1.5 * GAUSS_SKIP) && lp.rad > 1.6) {
                    if (fft || lp.rad > 30.0) {
                        if (lp.chromet == 0) {
                            ImProcFunctions::fftw_convol_blur2(tmp1->L, tmp1->L, bfwr, bfhr, radius, 0, 0);
                        } else if (lp.chromet == 1) {
                            ImProcFunctions::fftw_convol_blur2(tmp1->a, tmp1->a, bfwr, bfhr, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(tmp1->b, tmp1->b, bfwr, bfhr, radius, 0, 0);
                        } else if (lp.chromet == 2) {
                            ImProcFunctions::fftw_convol_blur2(tmp1->L, tmp1->L, bfwr, bfhr, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(tmp1->a, tmp1->a, bfwr, bfhr, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(tmp1->b, tmp1->b, bfwr, bfhr, radius, 0, 0);
                        }
                    } else {
#ifdef _OPENMP
                        #pragma omp parallel if (multiThread)
#endif
                        {
                            if (lp.chromet == 0)
                            {
                                gaussianBlur(tmp1->L, tmp1->L, bfw, bfh, radius);
                            } else if (lp.chromet == 1)
                            {
                                gaussianBlur(tmp1->a, tmp1->a, bfw, bfh, radius);
                                gaussianBlur(tmp1->b, tmp1->b, bfw, bfh, radius);
                            } else if (lp.chromet == 2)
                            {
                                gaussianBlur(tmp1->L, tmp1->L, bfw, bfh, radius);
                                gaussianBlur(tmp1->a, tmp1->a, bfw, bfh, radius);
                                gaussianBlur(tmp1->b, tmp1->b, bfw, bfh, radius);
                            }
                        }
                    }

                } else if (lp.blurmet == 1 && lp.blmet == 0 && static_cast<double>(radius) > (1.5 * GAUSS_SKIP) && lp.rad > 1.6) {
                    if (fft || lp.rad > 30.0) {
                        if (lp.chromet == 0) {
                            ImProcFunctions::fftw_convol_blur2(original->L, tmp1->L, TW, TH, radius, 0, 0);
                        } else if (lp.chromet == 1) {
                            ImProcFunctions::fftw_convol_blur2(original->a, tmp1->a, TW, TH, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(original->b, tmp1->b, TW, TH, radius, 0, 0);
                        } else if (lp.chromet == 2) {
                            ImProcFunctions::fftw_convol_blur2(original->L, tmp1->L, TW, TH, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(original->a, tmp1->a, TW, TH, radius, 0, 0);
                            ImProcFunctions::fftw_convol_blur2(original->b, tmp1->b, TW, TH, radius, 0, 0);
                        }
                    } else {
#ifdef _OPENMP
                        #pragma omp parallel if (multiThread)
#endif
                        {
                            if (lp.chromet == 0)
                            {
                                gaussianBlur(original->L, tmp1->L, TW, TH, radius);
                            } else if (lp.chromet == 1)
                            {
                                gaussianBlur(original->a, tmp1->a, TW, TH, radius);
                                gaussianBlur(original->b, tmp1->b, TW, TH, radius);
                            } else if (lp.chromet == 2)
                            {
                                gaussianBlur(original->L, tmp1->L, TW, TH, radius);
                                gaussianBlur(original->a, tmp1->a, TW, TH, radius);
                                gaussianBlur(original->b, tmp1->b, TW, TH, radius);
                            }
                        }
                    }
                }

                //add noise
                if (tmp1.get() && lp.stren > 0.1 && lp.blmet == 0) {
                    float mean = 0.f;//0 best result
                    float variance = lp.stren ;
                    addGaNoise(tmp1.get(), tmp1.get(), mean, variance, sk) ;
                }

                //add grain
                if (lp.blmet == 0 && strengr > 0) {
                    int wi = bfw;
                    int he = bfh;

                    if (lp.blurmet == 1) {
                        wi = TW;
                        he = TH;
                    }

                    if (tmp1.get()) {
                        Imagefloat *tmpImage = nullptr;
                        tmpImage = new Imagefloat(wi, he);

                        for (int y = 0; y < he ; y++) {
                            for (int x = 0; x < wi; x++) {
                                tmpImage->g(y, x) = tmp1->L[y][x];
                                tmpImage->r(y, x) = tmp1->a[y][x];
                                tmpImage->b(y, x) = tmp1->b[y][x];
                            }
                        }


                        filmGrain(tmpImage, isogr, strengr, scalegr, divgr, wi, he, call, fw, fh);

                        for (int y = 0; y < he ; y++) {
                            for (int x = 0; x < wi; x++) {
                                tmp1->L[y][x] = tmpImage->g(y, x);
                                tmp1->a[y][x] = tmpImage->r(y, x);
                                tmp1->b[y][x] = tmpImage->b(y, x);
                            }
                        }

                        delete tmpImage;
                    }
                }
                //recovery with mask luminance
                if (lp.blurmet == 0  && lp.blmet == 0) {//for blur, grain, noise
                    if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {                      
                        recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, bfw, bfh, xstart, ystart, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);

                    }
                }//inverse
                if (lp.blurmet == 1 && lp.blmet == 0) {                 
                    if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {                      
                        recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, TW, TH, 0, 0, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);
                    }
                }
                
                Median medianTypeL = Median::TYPE_3X3_STRONG;
                Median medianTypeAB = Median::TYPE_3X3_STRONG;
                //median blur
                if (lp.medmet == 0) {
                    medianTypeL = medianTypeAB = Median::TYPE_3X3_STRONG;
                } else if (lp.medmet == 1) {
                    medianTypeL = medianTypeAB = Median::TYPE_5X5_STRONG;
                } else if (lp.medmet == 2) {
                    medianTypeL = medianTypeAB = Median::TYPE_7X7;
                } else if (lp.medmet == 3) {
                    medianTypeL = medianTypeAB = Median::TYPE_9X9;
                }

                if (lp.blurmet == 0 && lp.blmet == 1 && lp.medmet != -1) {
                    float** tmL;
                    int wid = bfw;
                    int hei = bfh;
                    tmL = new float*[hei];

                    for (int i = 0; i < hei; ++i) {
                        tmL[i] = new float[wid];
                    }

                    if (lp.chromet == 0) {
                        Median_Denoise(tmp1->L, tmp1->L, bfw, bfh, medianTypeL, lp.it, multiThread, tmL);
                    }

                    else if (lp.chromet == 1) {
                        Median_Denoise(tmp1->a, tmp1->a, bfw, bfh, medianTypeAB, lp.it, multiThread, tmL);
                        Median_Denoise(tmp1->b, tmp1->b, bfw, bfh, medianTypeAB, lp.it, multiThread, tmL);
                    } else if (lp.chromet == 2) {
                        Median_Denoise(tmp1->L, tmp1->L, bfw, bfh, medianTypeL, lp.it, multiThread, tmL);
                        Median_Denoise(tmp1->a, tmp1->a, bfw, bfh, medianTypeAB, lp.it, multiThread, tmL);
                        Median_Denoise(tmp1->b, tmp1->b, bfw, bfh, medianTypeAB, lp.it, multiThread, tmL);
                    }

                    for (int i = 0; i < hei; ++i) {
                        delete[] tmL[i];
                    }

                    delete[] tmL;
                    //recovery with mask luminance
                    if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {
                        recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, bfw, bfh, xstart, ystart, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);
                    }
                    
                } else if (lp.blurmet == 1 && lp.blmet == 1) {
                    float** tmL;
                    int wid = TW;
                    int hei = TH;
                    tmL = new float*[hei];

                    for (int i = 0; i < hei; ++i) {
                        tmL[i] = new float[wid];
                    }

                    if (lp.chromet == 0) {
                        Median_Denoise(tmp2->L, tmp1->L, TW, TH, medianTypeL, lp.it, multiThread, tmL);
                    } else if (lp.chromet == 1) {
                        Median_Denoise(tmp2->a, tmp1->a, TW, TH, medianTypeAB, lp.it, multiThread, tmL);
                        Median_Denoise(tmp2->b, tmp1->b, TW, TH, medianTypeAB, lp.it, multiThread, tmL);
                    } else if (lp.chromet == 2) {
                        Median_Denoise(tmp2->L, tmp1->L, TW, TH, medianTypeL, lp.it, multiThread, tmL);
                        Median_Denoise(tmp2->a, tmp1->a, TW, TH, medianTypeAB, lp.it, multiThread, tmL);
                        Median_Denoise(tmp2->b, tmp1->b, TW, TH, medianTypeAB, lp.it, multiThread, tmL);
                    }

                    for (int i = 0; i < hei; ++i) {
                        delete[] tmL[i];
                    }

                    delete[] tmL;
                    if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {
                        recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, TW, TH, 0, 0, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);
                    }                    
                }

                if (lp.blurmet == 0 && lp.blmet == 2) {

                    if (lp.guidb > 0) {
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = ystart; y < yend ; y++) {
                            for (int x = xstart; x < xend; x++) {
                                tmp1->L[y - ystart][x - xstart] = original->L[y][x];
                                tmp1->a[y - ystart][x - xstart] = original->a[y][x];
                                tmp1->b[y - ystart][x - xstart] = original->b[y][x];
                                tmp3->L[y - ystart][x - xstart] = original->L[y][x];
                                tmp3->a[y - ystart][x - xstart] = original->a[y][x];
                                tmp3->b[y - ystart][x - xstart] = original->b[y][x];
                            }
                        }

                        Imagefloat *tmpImage = nullptr;
                        tmpImage = new Imagefloat(bfw, bfh);
                        lab2rgb(*tmp1, *tmpImage, params->icm.workingProfile);
                        array2D<float> LL(bfw, bfh);
                        array2D<float> rr(bfw, bfh);
                        array2D<float> gg(bfw, bfh);
                        array2D<float> bb(bfw, bfh);
                        array2D<float> guide(bfw, bfh);


#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh ; y++) {
                            for (int x = 0; x < bfw; x++) {
                                LL[y][x] = tmp1->L[y][x];
                                float ll = LL[y][x] / 32768.f;
                                guide[y][x] = xlin2log(rtengine::max(ll, 0.f), 10.f);
                                rr[y][x] = tmpImage->r(y, x);
                                gg[y][x] = tmpImage->g(y, x);
                                bb[y][x] = tmpImage->b(y, x);

                            }
                        }

                        array2D<float> iR(bfw, bfh, rr, 0);
                        array2D<float> iG(bfw, bfh, gg, 0);
                        array2D<float> iB(bfw, bfh, bb, 0);
                        array2D<float> iL(bfw, bfh, LL, 0);

                        int r = rtengine::max(int(lp.guidb / sk), 1);

                        const float epsil = 0.001f * std::pow(2.f, -lp.epsb);

                        if (lp.chromet == 0) {
                            rtengine::guidedFilterLog(guide, 10.f, LL, r, epsil, multiThread);
                        } else if (lp.chromet == 1) {
                            rtengine::guidedFilterLog(guide, 10.f, rr, r, epsil, multiThread);
                            rtengine::guidedFilterLog(guide, 10.f, bb, r, epsil, multiThread);
                        } else if (lp.chromet == 2) {
                            rtengine::guidedFilterLog(10.f, gg, r, epsil, multiThread);
                            rtengine::guidedFilterLog(10.f, rr, r, epsil, multiThread);
                            rtengine::guidedFilterLog(10.f, bb, r, epsil, multiThread);
                        }

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh ; y++) {
                            for (int x = 0; x < bfw; x++) {
                                rr[y][x] = intp(lp.strbl, rr[y][x], iR[y][x]);
                                gg[y][x] = intp(lp.strbl, gg[y][x], iG[y][x]);
                                bb[y][x] = intp(lp.strbl, bb[y][x], iB[y][x]);
                                tmpImage->r(y, x) = rr[y][x];
                                tmpImage->g(y, x) = gg[y][x];
                                tmpImage->b(y, x) = bb[y][x];

                            }
                        }

                        rgb2lab(*tmpImage, *tmp1, params->icm.workingProfile);

                        if (lp.chromet == 0) {
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfh ; y++) {
                                for (int x = 0; x < bfw; x++) {
                                    LL[y][x] = intp(lp.strbl, LL[y][x], iL[y][x]);
                                    tmp1->L[y][x] = LL[y][x];
                                }
                            }
                        }
                        //new recovery with mask luminance guided filter
                        if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {
                            recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, bfw, bfh, xstart, ystart, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);
                        }

                        delete tmpImage;
                    }


                } else if (lp.blurmet == 1 && lp.blmet == 2) {

                    if (lp.guidb > 0) {
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < TH ; y++) {
                            for (int x = 0; x < TW; x++) {
                                tmp1->L[y][x] = original->L[y][x];
                                tmp1->a[y][x] = original->a[y][x];
                                tmp1->b[y][x] = original->b[y][x];
                                tmp2->L[y][x] = original->L[y][x];
                                tmp3->L[y][x] = original->L[y][x];
                                tmp3->a[y][x] = original->a[y][x];
                                tmp3->b[y][x] = original->b[y][x];
                            }
                        }

                        Imagefloat *tmpImage = nullptr;
                        tmpImage = new Imagefloat(TW, TH);
                        lab2rgb(*tmp1, *tmpImage, params->icm.workingProfile);
                        array2D<float> LL(TW, TH);
                        array2D<float> rr(TW, TH);
                        array2D<float> gg(TW, TH);
                        array2D<float> bb(TW, TH);
                        array2D<float> guide(TW, TH);

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < TH ; y++) {
                            for (int x = 0; x < TW; x++) {
                                LL[y][x] = tmp1->L[y][x];
                                float ll = LL[y][x] / 32768.f;
                                guide[y][x] = xlin2log(rtengine::max(ll, 0.f), 10.f);
                                rr[y][x] = tmpImage->r(y, x);
                                gg[y][x] = tmpImage->g(y, x);
                                bb[y][x] = tmpImage->b(y, x);

                            }
                        }

                        array2D<float> iR(TW, TH, rr, 0);
                        array2D<float> iG(TW, TH, gg, 0);
                        array2D<float> iB(TW, TH, bb, 0);
                        array2D<float> iL(TW, TH, LL, 0);

                        int r = rtengine::max(int(lp.guidb / sk), 1);

                        const float epsil = 0.001f * std::pow(2.f, - lp.epsb);

                        if (lp.chromet == 0) {
                            rtengine::guidedFilterLog(guide, 10.f, LL, r, epsil, multiThread);
                        } else if (lp.chromet == 1) {
                            rtengine::guidedFilterLog(guide, 10.f, rr, r, epsil, multiThread);
                            rtengine::guidedFilterLog(guide, 10.f, bb, r, epsil, multiThread);
                        } else if (lp.chromet == 2) {
                            rtengine::guidedFilterLog(10.f, gg, r, epsil, multiThread);
                            rtengine::guidedFilterLog(10.f, rr, r, epsil, multiThread);
                            rtengine::guidedFilterLog(10.f, bb, r, epsil, multiThread);
                        }

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < TH ; y++) {
                            for (int x = 0; x < TW; x++) {
                                rr[y][x] = intp(lp.strbl, rr[y][x], iR[y][x]);
                                gg[y][x] = intp(lp.strbl, gg[y][x], iG[y][x]);
                                bb[y][x] = intp(lp.strbl, bb[y][x], iB[y][x]);
                                tmpImage->r(y, x) = rr[y][x];
                                tmpImage->g(y, x) = gg[y][x];
                                tmpImage->b(y, x) = bb[y][x];

                            }
                        }

                        rgb2lab(*tmpImage, *tmp1, params->icm.workingProfile);

                        if (lp.chromet == 0) {
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < TH ; y++) {
                                for (int x = 0; x < TW; x++) {
                                    LL[y][x] = intp(lp.strbl, LL[y][x], iL[y][x]);
                                    tmp1->L[y][x] = LL[y][x];
                                }
                            }
                        }
                        //recovery with mask luminance
                        if (lp.enablMask && lp.recothr != 1.f && lp.smasktyp != 1) {
                            recovm(lp.higthr, lp.lowthr, lp.recothr, lp.invmask, TW, TH, 0, 0, 2.f, sk,  bufmaskblurbl.get(), tmp1.get(), tmp3.get(), multiThread);
                        }

                        delete tmpImage;
                    }
                }

                if (tmp1.get()) {
                    if (lp.blurmet == 0) { //blur and noise (center)

                        if (lp.smasktyp != 1) {
                            BlurNoise_Local(tmp1.get(), originalmaskbl.get(),  hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);
                        } else {
                            BlurNoise_Local(tmp1.get(), original, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);
                        }

                        if (lp.recur) {
                            original->CopyFrom(transformed, multiThread);
                            float avge;
                            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                        }
                    } else if (lp.blurmet == 1) {
//                       InverseBlurNoise_Local(originalmaskbl, bufchro, lp, hueref, chromaref, lumaref, original, transformed, tmp1.get(), cx, cy, sk);
                        if (lp.smasktyp != 1) {
                            InverseBlurNoise_Local(originalmaskbl.get(), lp, hueref, chromaref, lumaref, original, transformed, tmp1.get(), cx, cy, sk);
                        } else {
                            InverseBlurNoise_Local(original, lp, hueref, chromaref, lumaref, original, transformed, tmp1.get(), cx, cy, sk);
                        }

                        if (lp.recur) {
                            original->CopyFrom(transformed, multiThread);
                            float avge;
                            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                        }
                    }
                }
            }
        }
    }

    //local impulse
    if ((lp.bilat > 0.f) && lp.denoiena) {
        const int bfh = int (lp.ly + lp.lyT) + del; //bfw bfh real size of square zone
        const int bfw = int (lp.lx + lp.lxL) + del;

        std::unique_ptr<LabImage> bufwv;

        if (call == 2) {//simpleprocess
            bufwv.reset(new LabImage(bfw, bfh)); //buffer for data in zone limit

            const int begy = lp.yc - lp.lyT;
            const int begx = lp.xc - lp.lxL;
            const int yEn = lp.yc + lp.ly;
            const int xEn = lp.xc + lp.lx;

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = rtengine::max(0, begy - cy); y < rtengine::min(transformed->H, yEn - cy); y++) {
                const int loy = cy + y;

                for (int x = rtengine::max(0, begx - cx); x < rtengine::min(transformed->W, xEn - cx); x++) {
                    const int lox = cx + x;
                    bufwv->L[loy - begy][lox - begx] = original->L[y][x];
                    bufwv->a[loy - begy][lox - begx] = original->a[y][x];
                    bufwv->b[loy - begy][lox - begx] = original->b[y][x];
                }
            }
        } else {//dcrop.cc
            bufwv.reset(new LabImage(transformed->W, transformed->H));
            bufwv->CopyFrom(original, multiThread);
        } //end dcrop

        const double threshold = lp.bilat / 20.f;

        if (bfh > 8 && bfw > 8) {
            ImProcFunctions::impulse_nr(bufwv.get(), threshold);
        }

        DeNoise_Local(call, lp,  originalmaskbl.get(), levred, huerefblur, lumarefblur, chromarefblur, original, transformed, *bufwv, cx, cy, sk);

        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }
    }

//local denoise
    if (lp.activspot && lp.denoiena && (lp.noiself > 0.f || lp.noiself0 > 0.f || lp.noiself2 > 0.f || lp.wavcurvedenoi ||lp.nlstr > 0 || lp.noiselc > 0.f || lp.noisecf > 0.f || lp.noisecc > 0.f )) {//disable denoise if not used
        constexpr int aut = 0;
        DeNoise(sp, call, aut, noiscfactiv, lp, originalmaskbl.get(), bufmaskblurbl.get(), levred, huerefblur, lumarefblur, chromarefblur, original, transformed, cx, cy, sk, locwavCurvehue, locwavhueutili, locwavCurvehuecont, locwavhueutilicont,
               resi, denocont, savmadl);
        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }
    }

//Tone mapping

    if ((lp.strengt != 0.f || lp.showmasktmmet == 2 || lp.enatmMask || lp.showmasktmmet == 3 || lp.showmasktmmet == 4 || lp.prevdE) && lp.tonemapena && !params->epd.enabled) {
        if (call <= 3) { //simpleprocess dcrop improcc
            const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
            const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
            const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
            const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
            const int bfh = yend - ystart;
            const int bfw = xend - xstart;

            if (bfw >= mDEN && bfh >= mDEN) {
                array2D<float> buflight(bfw, bfh);
                JaggedArray<float> bufchro(bfw, bfh);
                std::unique_ptr<LabImage> bufgb(new LabImage(bfw, bfh));
                const std::unique_ptr<LabImage> tmp1(new LabImage(bfw, bfh));
                const std::unique_ptr<LabImage> bufgbm(new LabImage(bfw, bfh));
                const std::unique_ptr<LabImage> tmp1m(new LabImage(bfw, bfh));
                std::unique_ptr<LabImage> bufmaskorigtm;
                std::unique_ptr<LabImage> bufmaskblurtm;
                std::unique_ptr<LabImage> originalmasktm;

                //       if (lp.showmasktmmet == 0 || lp.showmasktmmet == 2  || lp.enatmMask || lp.showmasktmmet == 3 || lp.showmasktmmet == 4) {
                if (lp.showmasktmmet == 2  || lp.enatmMask || lp.showmasktmmet == 3 || lp.showmasktmmet == 4) {
                    bufmaskorigtm.reset(new LabImage(bfw, bfh));
                    bufmaskblurtm.reset(new LabImage(bfw, bfh));
                    originalmasktm.reset(new LabImage(bfw, bfh));
                }

                // 3 loops to avoid performance penalty on machines with 4-way L1 cache
#ifdef _OPENMP
                #pragma omp parallel if (multiThread)
                {
                    #pragma omp for schedule(dynamic,16) nowait
#endif

                    for (int y = ystart; y < yend; y++) {
                        for (int x = xstart; x < xend; x++) {
                            bufgbm->L[y - ystart][x - xstart] = bufgb->L[y - ystart][x - xstart] = original->L[y][x];
                        }
                    }

#ifdef _OPENMP
                    #pragma omp for schedule(dynamic,16) nowait
#endif

                    for (int y = ystart; y < yend; y++) {
                        for (int x = xstart; x < xend; x++) {
                            bufgbm->a[y - ystart][x - xstart] = bufgb->a[y - ystart][x - xstart] = original->a[y][x];
                        }
                    }

#ifdef _OPENMP
                    #pragma omp for schedule(dynamic,16)
#endif

                    for (int y = ystart; y < yend; y++) {
                        for (int x = xstart; x < xend; x++) {
                            bufgbm->b[y - ystart][x - xstart] = bufgb->b[y - ystart][x - xstart] = original->b[y][x];
                        }
                    }

#ifdef _OPENMP
                }
#endif

                int inv = 0;
                bool showmaske = false;
                bool enaMask = false;
                bool deltaE = false;
                bool modmask = false;
                bool zero = false;
                bool modif = false;

                if (lp.showmasktmmet == 3) {
                    showmaske = true;
                }

                if (lp.enatmMask) {
                    enaMask = true;
                }

                if (lp.showmasktmmet == 4) {
                    deltaE = true;
                }

                if (lp.showmasktmmet == 2) {
                    modmask = true;
                }

                if (lp.showmasktmmet == 1) {
                    modif = true;
                }

                if (lp.showmasktmmet == 0) {
                    zero = true;
                }

                float chrom = lp.chromatm;;
                float rad = lp.radmatm;
                float gamma = lp.gammatm;
                float slope = lp.slomatm;
                float blendm = lp.blendmatm;
                float lap = params->locallab.spots.at(sp).lapmasktm;
                bool pde = params->locallab.spots.at(sp).laplac;
                int lumask = params->locallab.spots.at(sp).lumask;

                if (!params->locallab.spots.at(sp).enatmMaskaft) {
                    LocwavCurve dummy;
                    int sco = params->locallab.spots.at(sp).scopemask;
                    int shortcu = 0; //lp.mergemet;// params->locallab.spots.at(sp).shortc;

                    const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                    const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                    const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                    const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                    int shado = 0;
                    float amountcd = 0.f;
                    float anchorcd = 50.f;
                    LocHHmaskCurve lochhhmasCurve;
                    const int highl = 0;
                    maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufgbm.get(), bufmaskorigtm.get(), originalmasktm.get(), original, reserved, inv, lp,
                                0.f, false,
                                locccmastmCurve, lcmastmutili, locllmastmCurve, llmastmutili, lochhmastmCurve, lhmastmutili, lochhhmasCurve, false, multiThread,
                                enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmasktmlocalcurve, localmasktmutili, dummy, false, 1, 1, 5, 5,
                                shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                                maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                               );

                    if (lp.showmasktmmet == 3) {
                        showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufgbm.get(), transformed, bufmaskorigtm.get(), 0);

                        return;
                    }
                }

                if (lp.showmasktmmet == 0 || lp.showmasktmmet == 1  || lp.showmasktmmet == 2 || lp.showmasktmmet == 4 || lp.showmasktmmet == 3 || lp.enatmMask) {
                    constexpr int itera = 0;
                    ImProcFunctions::EPDToneMaplocal(sp, bufgb.get(), tmp1.get(), itera, sk);//iterate to 0 calculate with edgstopping, improve result, call=1 dcrop we can put iterate to 5

                    if (params->locallab.spots.at(sp).expcie && params->locallab.spots.at(sp).modecie == "tm") {
                        bool HHcurvejz = false;
                        bool CHcurvejz = false;
                        bool LHcurvejz = false;

                        if (params->locallab.spots.at(sp).modecam == "jz") {//some cam16 elementsfor Jz
                            ImProcFunctions::ciecamloc_02float(lp, sp, tmp1.get(), bfw, bfh, 10, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                        }

                        ImProcFunctions::ciecamloc_02float(lp, sp, tmp1.get(), bfw, bfh, 0, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);

                        float rad = params->locallab.spots.at(sp).detailcie;
                        loccont(bfw, bfh, tmp1.get(), rad, 15.f, sk);
                    }




                    tmp1m->CopyFrom(tmp1.get(), multiThread); //save current result7

                    if (params->locallab.spots.at(sp).equiltm  && params->locallab.spots.at(sp).exptonemap) {
                        if (call == 3) {
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = ystart; y < yend; y++) {
                                for (int x = xstart; x < xend; x++) {
                                    savenormtm->L[y][x] = tmp1->L[y - ystart][x - xstart];
                                    savenormtm->a[y][x] = tmp1->a[y - ystart][x - xstart];
                                    savenormtm->b[y][x] = tmp1->b[y - ystart][x - xstart];
                                }
                            }
                        }
                    }

                    bool enatmMasktmap = params->locallab.spots.at(sp).enatmMaskaft;

                    if (enatmMasktmap) {
                        //calculate new values for original, originalmasktm, bufmaskorigtm...in function of tmp1
                        LocwavCurve dummy;
                        int sco = params->locallab.spots.at(sp).scopemask;
                        int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;

                        const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                        const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                        int shado = 0;
                        float amountcd = 0.f;
                        float anchorcd = 50.f;
                        LocHHmaskCurve lochhhmasCurve;
                        const int highl = 0;
                        maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, tmp1.get(), bufmaskorigtm.get(), originalmasktm.get(), original, reserved, inv, lp,
                                    0.f, false,
                                    locccmastmCurve, lcmastmutili, locllmastmCurve, llmastmutili, lochhmastmCurve, lhmastmutili, lochhhmasCurve, false, multiThread,
                                    enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmasktmlocalcurve, localmasktmutili, dummy, false, 1, 1, 5, 5,
                                    shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                                    maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                                   );

                        if (lp.showmasktmmet == 3) {//display mask
                            showmask(params->locallab.spots.at(sp).lumask, lp, xstart, ystart, cx, cy, bfw, bfh, tmp1.get(), transformed, bufmaskorigtm.get(), 0);
                            return;
                        }

                    }

                    tmp1->CopyFrom(tmp1m.get(), multiThread); //restore current result


                    float minL = tmp1->L[0][0] - bufgb->L[0][0];
                    float maxL = minL;
                    float minC = std::sqrt(SQR(tmp1->a[0][0]) + SQR(tmp1->b[0][0])) - std::sqrt(SQR(bufgb->a[0][0]) + SQR(bufgb->b[0][0]));
                    float maxC = minC;

#ifdef _OPENMP
                    #pragma omp parallel for reduction(max:maxL) reduction(min:minL) reduction(max:maxC) reduction(min:minC) schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            buflight[ir][jr] = tmp1->L[ir][jr] - bufgb->L[ir][jr];
                            minL = rtengine::min(minL, buflight[ir][jr]);
                            maxL = rtengine::max(maxL, buflight[ir][jr]);
                            bufchro[ir][jr] = std::sqrt(SQR(tmp1->a[ir][jr]) + SQR(tmp1->b[ir][jr])) - std::sqrt(SQR(bufgb->a[ir][jr]) + SQR(bufgb->b[ir][jr]));
                            minC = rtengine::min(minC, bufchro[ir][jr]);
                            maxC = rtengine::max(maxC, bufchro[ir][jr]);
                        }
                    }

                    float coef = 0.01f * rtengine::max(std::fabs(minL), std::fabs(maxL));
                    float coefC = 0.01f * rtengine::max(std::fabs(minC), std::fabs(maxC));

                    if (coef == 0.f) {
                        coef = 1.f;
                    } else {
                        coef = 1.f /  coef;
                    }

                    if (coefC == 0.f) {
                        coefC = 1.f;
                    } else {
                        coefC = 1.f / coefC;
                    }

#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            buflight[y][x] *= coef;
                            bufchro[y][x] *= coefC;
                        }
                    }

                    if (lp.enatmMask && lp.recothrt != 1.f) {
                        float recoth = lp.recothrt;

                        if (lp.recothrt < 1.f) {
                            recoth = -1.f * recoth + 2.f;
                        }

                        float hig = lp.higthrt;
                        float low = lp.lowthrt;
                        // float recoth = lp.recothrt;
                        float decay = lp.decayt;
                        bool invmask = false;
                        maskrecov(tmp1.get(), original, bufmaskorigtm.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                    }

                    //   transit_shapedetect_retinex(call, 4, bufgb.get(),bufmaskorigtm.get(), originalmasktm.get(), buflight, bufchro, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);

                    if (lp.recothrt >= 1.f) {
                        transit_shapedetect2(sp, meantm, stdtm, call, 8, bufgb.get(), tmp1.get(), originalmasktm.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD,  cx, cy, sk);
                    } else {
                        transit_shapedetect2(sp, meantm, stdtm, call, 8, bufgb.get(), tmp1.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                    }

                    //  transit_shapedetect(8, tmp1.get(), originalmasktm.get(), bufchro, false, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, cx, cy, sk);
                    bufgb.reset();

                    if (lp.recur) {
                        original->CopyFrom(transformed, multiThread);
                        float avge;
                        calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                    }
                }
            }
        }
    }

//end TM


    if ((lp.dehaze != 0 || lp.prevdE) && lp.retiena) {
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;

        if (bfh >= mSP && bfw >= mSP) {
            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh)); //buffer for data in zone limit
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh)); //buffer for data in zone limit

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend; y++) {
                for (int x = xstart; x < xend; x++) {
                    bufexporig->L[y - ystart][x - xstart] = original->L[y][x];
                    bufexporig->a[y - ystart][x - xstart] = original->a[y][x];
                    bufexporig->b[y - ystart][x - xstart] = original->b[y][x];
                }
            }

            bufexpfin->CopyFrom(bufexporig.get(), multiThread);
            //calc dehaze
            const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));

            DehazeParams dehazeParams;
            dehazeParams.enabled = true;
            dehazeParams.strength = lp.dehaze;
            dehazeParams.showDepthMap = false;
            dehazeParams.saturation = lp.dehazeSaturation;
            dehazeParams.depth = lp.depth;
            lab2rgb(*bufexpfin, *tmpImage.get(), params->icm.workingProfile);
            dehazeloc(tmpImage.get(), dehazeParams, sk, sp);
            rgb2lab(*tmpImage.get(), *bufexpfin, params->icm.workingProfile);

            transit_shapedetect2(sp, 0.f, 0.f, call, 30, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }

    lp.invret = false;//always disabled inverse RETI   too complex todo !!

    if (lp.str >= 0.2f && lp.retiena && call != 2) {
        LabImage *bufreti = nullptr;
        LabImage *bufmask = nullptr;
        LabImage *buforig = nullptr;
        LabImage *buforigmas = nullptr;
        LabImage *bufmaskorigreti = nullptr;

        if (TW >= mSP && TH >= mSP) {

            array2D<float> buflight(TW, TH);
            JaggedArray<float> bufchro(TW, TH);

            int Hd, Wd;
            Hd = TH;
            Wd = TW;

            bufreti = new LabImage(TW, TH);
            bufmask = new LabImage(TW, TH);
            bufmaskorigreti = new LabImage(TW, TH);

            if (!lp.enaretiMasktmap && lp.enaretiMask) {
                buforig = new LabImage(TW, TH);
                buforigmas = new LabImage(TW, TH);
                //  bufmaskorigreti = new LabImage(GW, GH);
            }

#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int ir = 0; ir < TH; ir++) //fill with 0
                for (int jr = 0; jr < TW; jr++) {
                    bufreti->L[ir][jr] = 0.f;
                    bufreti->a[ir][jr] = 0.f;
                    bufreti->b[ir][jr] = 0.f;
                    buflight[ir][jr] = 0.f;
                    bufchro[ir][jr] = 0.f;
                }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H ; y++) //{
                for (int x = 0; x < transformed->W; x++) {
                    bufreti->L[y][x] = original->L[y][x];
                    bufreti->a[y][x] = original->a[y][x];
                    bufreti->b[y][x] = original->b[y][x];
                    bufmask->L[y][x] = original->L[y][x];
                    bufmask->a[y][x] = original->a[y][x];
                    bufmask->b[y][x] = original->b[y][x];



                    if (!lp.enaretiMasktmap && lp.enaretiMask) {
                        buforig->L[y][x] = original->L[y][x];
                        buforig->a[y][x] = original->a[y][x];
                        buforig->b[y][x] = original->b[y][x];

                        //   bufmaskorigreti->L[y][x] = original->L[y][x];
                        //   bufmaskorigreti->a[y][x] = original->a[y][x];
                        //   bufmaskorigreti->b[y][x] = original->b[y][x];


                    }

                }

            float raddE = params->locallab.spots.at(sp).softradiusret;

            //calc dE and reduction to use in MSR to reduce artifacts
            const float mindE = 4.f + MINSCOPE * lp.sensh * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * lp.sensh * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            const float refa = chromaref * cos(hueref);
            const float refb = chromaref * sin(hueref);

            const std::unique_ptr<JaggedArray<float>> reducDEBuffer(new JaggedArray<float>(Wd, Hd));
            float** reducDE = *reducDEBuffer;

            float ade = 0.01f * raddE;
            float bde = 100.f - raddE;
            float sensibefore = ade * lp.sensh + bde;//we can change sensitivity 0.1 90 or 0.3 70 or 0.4 60
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H ; y++)
                for (int x = 0; x < transformed->W; x++) {
                    float dE = std::sqrt(SQR(refa - bufreti->a[y][x] / 327.68f) + SQR(refb - bufreti->b[y][x] / 327.68f) + SQR(static_cast<float>(lumaref) - bufreti->b[y][x] / 327.68f));
                    float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sensibefore);
                    if(lp.fullim == 3 ) {//disable scope
                        reducdE = 1.f;
                    }
                    
                    reducDE[y][x] = clipDE(reducdE);
                }

            const std::unique_ptr<JaggedArray<float>> origBuffer(new JaggedArray<float>(Wd, Hd));
            float** orig = *origBuffer;

            const std::unique_ptr<JaggedArray<float>> origBuffer1(new JaggedArray<float>(Wd, Hd));
            float** orig1 = *origBuffer1;

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < Hd; ir += 1)
                for (int jr = 0; jr < Wd; jr += 1) {
                    orig[ir][jr] = bufreti->L[ir][jr];
                    orig1[ir][jr] = bufreti->L[ir][jr];
                }

            LabImage *tmpl = new LabImage(Wd, Hd);

            bool fftw = lp.ftwreti;
            //for Retinex Mask are incorporated in MSR
            int sco = params->locallab.spots.at(sp).scopemask;
            float lumask = params->locallab.spots.at(sp).lumask;

            const float mindE2 = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE2 = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim2 = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim2 = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            ImProcFunctions::MSRLocal(call, sp, fftw, 1, reducDE, bufreti, bufmask, buforig, buforigmas, bufmaskorigreti, orig, orig1,
                                      Wd, Hd, Wd, Hd, params->locallab, sk, locRETgainCcurve, locRETtransCcurve, 0, 4, 1.f, minCD, maxCD, mini, maxi, Tmean, Tsigma, Tmin, Tmax,
                                      locccmasretiCurve, lcmasretiutili, locllmasretiCurve, llmasretiutili, lochhmasretiCurve, lhmasretiutili, llretiMask,
                                      lmaskretilocalcurve, localmaskretiutili,
                                      transformed, lp.enaretiMasktmap, lp.enaretiMask,
                                      params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                                      maxdE2, mindE2, maxdElim2, mindElim2, lp.iterat, limscope, sco, lp.balance, lp.balanceh, lumask);
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int ir = 0; ir < Hd; ir += 1) {
                for (int jr = 0; jr < Wd; jr += 1) {
                    tmpl->L[ir][jr] = orig[ir][jr];

                    if (params->locallab.spots.at(sp).equilret  && params->locallab.spots.at(sp).expreti) {
                        if (call == 3) {
                            savenormreti->L[ir][jr] = tmpl->L[ir][jr];
                        }
                    }
                }
            }

            if (lp.equret) { //equilibrate luminance before / after MSR
                float *datain = new float[Hd * Wd];
                float *data = new float[Hd * Wd];
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir += 1)
                    for (int jr = 0; jr < Wd; jr += 1) {
                        datain[ir * Wd + jr] = orig1[ir][jr];
                        data[ir * Wd + jr] = orig[ir][jr];
                    }

                if (params->locallab.spots.at(sp).equilret) {
                    if (call == 3) { //improccoordinator
                        normalize_mean_dt(data, datain, Hd * Wd, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
                    } else if (call == 1) { //dcrop
                        float ma =  meanreti;
                        float sa = stdreti;
                        float ma2 = (float) params->locallab.spots.at(sp).sensihs;
                        float sa2 = (float) params->locallab.spots.at(sp).sensiv;
                        //use normalize with mean and stdv
                        normalize_mean_dt(data, datain, Hd * Wd, 1.f, 1.f, ma, sa, ma2, sa2, 1.);

                    }
                }

#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir += 1)
                    for (int jr = 0; jr < Wd; jr += 1) {
                        tmpl->L[ir][jr] = data[ir * Wd + jr];
                    }

                delete [] datain;
                delete [] data;
            }

            if (lp.enaretiMask && lp.recothrr != 1.f) {
                float hig = lp.higthrr;
                float low = lp.lowthrr;
                float recoth = lp.recothrr;
                float decay = lp.decayr;
                bool invmask = false;
                maskrecov(tmpl, original, bufmaskorigreti, Hd, Wd, 0, 0, hig, low, recoth, decay, invmask, sk, multiThread);
            }

            float minL = tmpl->L[0][0] - bufreti->L[0][0];
            float maxL = minL;
#ifdef _OPENMP
            #pragma omp parallel for reduction(min:minL) reduction(max:maxL) schedule(dynamic,16) if (multiThread)
#endif

            for (int ir = 0; ir < Hd; ir++) {
                for (int jr = 0; jr < Wd; jr++) {
                    buflight[ir][jr] = tmpl->L[ir][jr] - bufreti->L[ir][jr];
                    minL = rtengine::min(minL, buflight[ir][jr]);
                    maxL = rtengine::max(maxL, buflight[ir][jr]);
                }
            }

            const float coef = 0.01f * rtengine::max(std::fabs(minL), std::fabs(maxL));

            for (int ir = 0; ir < Hd; ir++) {
                for (int jr = 0; jr < Wd; jr++) {
                    buflight[ir][jr] /= coef;
                }
            }

            transit_shapedetect_retinex(call, 4, bufreti, tmpl, bufmask, buforigmas, buflight, bufchro, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }

            if (params->locallab.spots.at(sp).chrrt > 0) {

                if (call == 1) {

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir += 1)
                        for (int jr = 0; jr < Wd; jr += 1) {

                            orig[ir][jr] = std::sqrt(SQR(bufreti->a[ir][jr]) + SQR(bufreti->b[ir][jr]));
                            orig1[ir][jr] = std::sqrt(SQR(bufreti->a[ir][jr]) + SQR(bufreti->b[ir][jr]));
                        }

                }

                float maxChro = orig1[0][0];
#ifdef _OPENMP
                #pragma omp parallel for reduction(max:maxChro) schedule(dynamic,16) if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir++) {
                    for (int jr = 0; jr < Wd; jr++) {
                        maxChro = rtengine::max(maxChro, orig1[ir][jr]);
                    }
                }

                float divchro = maxChro;

                //first step change saturation without Retinex ==> gain of time and memory
                float satreal = lp.str * static_cast<float>(params->locallab.spots.at(sp).chrrt) / 100.f;

                if (params->locallab.spots.at(sp).chrrt <= 0.2) {
                    satreal /= 10.f;
                }

                DiagonalCurve reti_satur({
                    DCT_NURBS,
                    0, 0,
                    0.2, 0.2f + satreal / 250.f,
                    0.6,  rtengine::min(1.f, 0.6f + satreal / 250.f),
                    1, 1
                });

                if (call == 1) {

#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir += 1)
                        for (int jr = 0; jr < Wd; jr += 1) {
                            const float Chprov = orig1[ir][jr];
                            float2 sincosval;
                            sincosval.y = Chprov == 0.0f ? 1.f : bufreti->a[ir][jr] / Chprov;
                            sincosval.x = Chprov == 0.0f ? 0.f : bufreti->b[ir][jr] / Chprov;

                            if (params->locallab.spots.at(sp).chrrt <= 100.0) { //first step
                                float buf = LIM01(orig[ir][jr] / divchro);
                                buf = reti_satur.getVal(buf);
                                buf *= divchro;
                                orig[ir][jr] = buf;
                            }

                            tmpl->a[ir][jr] = orig[ir][jr] * sincosval.y;
                            tmpl->b[ir][jr] = orig[ir][jr] * sincosval.x;
                        }

                    float minC = std::sqrt(SQR(tmpl->a[0][0]) + SQR(tmpl->b[0][0])) - orig1[0][0];
                    float maxC = minC;
#ifdef _OPENMP
                    #pragma omp parallel for reduction(min:minC) reduction(max:maxC) schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir++) {
                        for (int jr = 0; jr < Wd; jr++) {
                            bufchro[ir][jr] = std::sqrt(SQR(tmpl->a[ir][jr]) + SQR(tmpl->b[ir][jr])) - orig1[ir][jr];
                            minC = rtengine::min(minC, bufchro[ir][jr]);
                            maxC = rtengine::max(maxC, bufchro[ir][jr]);
                        }
                    }

                    float coefC = 0.01f * rtengine::max(std::fabs(minC), std::fabs(maxC));

                    if (coefC > 0.f) {
                        coefC = 1.f / coefC;
#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int ir = 0; ir < Hd; ir++) {
                            for (int jr = 0; jr < Wd; jr++) {
                                bufchro[ir][jr] *= coefC;
                            }
                        }
                    }
                }

                transit_shapedetect_retinex(call, 5, tmpl, tmpl, bufmask, buforigmas, buflight, bufchro, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }
            }

            delete tmpl;
            delete bufmask;
            delete bufmaskorigreti;

            if (!lp.enaretiMasktmap && lp.enaretiMask) {
                if (buforig) {
                    delete buforig;
                }

                if (buforigmas) {
                    delete buforigmas;
                }
            }

            delete  bufreti;
        }
    }



    if (lp.str >= 0.2f && lp.retiena && call == 2) {
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;

        LabImage *bufreti = nullptr;
        LabImage *bufmask = nullptr;
        LabImage *buforig = nullptr;
        LabImage *buforigmas = nullptr;
        LabImage *bufmaskorigreti = nullptr;
        int bfhr = bfh;
        int bfwr = bfw;

        if (bfw >= mSP && bfh > mSP) {
            if (lp.ftwreti) {
                optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            array2D<float> buflight(bfw, bfh);
            JaggedArray<float> bufchro(bfw, bfh);

            int Hd, Wd;
            Hd = TH;
            Wd = TW;

            if (!lp.invret && call == 2) {

                Hd = bfh;
                Wd = bfw;
                bufreti = new LabImage(bfw, bfh);
                bufmask = new LabImage(bfw, bfh);
                bufmaskorigreti = new LabImage(bfw, bfh);

                if (!lp.enaretiMasktmap && lp.enaretiMask) {
                    buforig = new LabImage(bfw, bfh);
                    buforigmas = new LabImage(bfw, bfh);
                }

#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int ir = 0; ir < bfh; ir++) //fill with 0
                    for (int jr = 0; jr < bfw; jr++) {
                        bufreti->L[ir][jr] = 0.f;
                        bufreti->a[ir][jr] = 0.f;
                        bufreti->b[ir][jr] = 0.f;
                        buflight[ir][jr] = 0.f;
                        bufchro[ir][jr] = 0.f;
                    }


#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = ystart; y < yend; y++) {
                    for (int x = xstart; x < xend; x++) {
                        bufreti->L[y - ystart][x - xstart] = original->L[y][x];
                        bufreti->a[y - ystart][x - xstart] = original->a[y][x];
                        bufreti->b[y - ystart][x - xstart] = original->b[y][x];
                        bufmask->L[y - ystart][x - xstart] = original->L[y][x];
                        bufmask->a[y - ystart][x - xstart] = original->a[y][x];
                        bufmask->b[y - ystart][x - xstart] = original->b[y][x];

                        if (!lp.enaretiMasktmap && lp.enaretiMask) {
                            buforig->L[y - ystart][x - xstart] = original->L[y][x];
                            buforig->a[y - ystart][x - xstart] = original->a[y][x];
                            buforig->b[y - ystart][x - xstart] = original->b[y][x];
                        }
                    }
                }
            }

            float raddE = params->locallab.spots.at(sp).softradiusret;

            //calc dE and reduction to use in MSR to reduce artifacts
            const float mindE = 4.f + MINSCOPE * lp.sensh * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * lp.sensh * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            const float refa = chromaref * cos(hueref);
            const float refb = chromaref * sin(hueref);

            const std::unique_ptr<JaggedArray<float>> reducDEBuffer(new JaggedArray<float>(Wd, Hd));
            float** reducDE = *reducDEBuffer;
            float ade = 0.01f * raddE;
            float bde = 100.f - raddE;
            float sensibefore = ade * lp.sensh + bde;//we can change sensitivity 0.1 90 or 0.3 70 or 0.4 60
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend ; y++) {
                for (int x = xstart; x < xend; x++) {
                    const float dE = std::sqrt(SQR(refa - bufreti->a[y - ystart][x - xstart] / 327.68f) + SQR(refb - bufreti->b[y - ystart][x - xstart] / 327.68f) + SQR(static_cast<float>(lumaref) - bufreti->b[y - ystart][x - xstart] / 327.68f));
                    float reducdE = calcreducdE(dE, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sensibefore);
                    if(lp.fullim == 3 ) {//disable scope
                        reducdE = 1.f;
                    }
                  
                    reducDE[y - ystart][x - xstart] = clipDE(reducdE);
                }
            }

            const std::unique_ptr<JaggedArray<float>> origBuffer(new JaggedArray<float>(Wd, Hd));
            float** orig = *origBuffer;

            const std::unique_ptr<JaggedArray<float>> origBuffer1(new JaggedArray<float>(Wd, Hd));
            float** orig1 = *origBuffer1;

            LabImage *tmpl = nullptr;

            if (!lp.invret && call == 2) {
#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir += 1) {
                    for (int jr = 0; jr < Wd; jr += 1) {
                        orig[ir][jr] = bufreti->L[ir][jr];
                        orig1[ir][jr] = bufreti->L[ir][jr];
                    }
                }

                tmpl = new LabImage(Wd, Hd);
            }

            //   float minCD, maxCD, mini, maxi, Tmean, Tsigma, Tmin, Tmax;
            bool fftw = lp.ftwreti;
            //for Retinex Mask are incorporated in MSR
            int sco = params->locallab.spots.at(sp).scopemask;
            float lumask = params->locallab.spots.at(sp).lumask;

            const float mindE2 = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE2 = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim2 = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim2 = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);

            ImProcFunctions::MSRLocal(call, sp, fftw, 1, reducDE, bufreti, bufmask, buforig, buforigmas, bufmaskorigreti, orig, orig1,
                                      Wd, Hd, bfwr, bfhr, params->locallab, sk, locRETgainCcurve, locRETtransCcurve, 0, 4, 1.f, minCD, maxCD, mini, maxi, Tmean, Tsigma, Tmin, Tmax,
                                      locccmasretiCurve, lcmasretiutili, locllmasretiCurve, llmasretiutili, lochhmasretiCurve, lhmasretiutili, llretiMask,
                                      lmaskretilocalcurve, localmaskretiutili,
                                      transformed, lp.enaretiMasktmap, lp.enaretiMask,
                                      params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                                      maxdE2, mindE2, maxdElim2, mindElim2, lp.iterat, limscope, sco, lp.balance, lp.balanceh, lumask);

#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif

            for (int ir = 0; ir < Hd; ir += 1)
                for (int jr = 0; jr < Wd; jr += 1) {
                    tmpl->L[ir][jr] = orig[ir][jr];
                }


            if (lp.equret) { //equilibrate luminance before / after MSR
                const std::unique_ptr<float[]> datain(new float[Hd * Wd]);
                const std::unique_ptr<float[]> data(new float[Hd * Wd]);
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir += 1) {
                    for (int jr = 0; jr < Wd; jr += 1) {
                        datain[ir * Wd + jr] = orig1[ir][jr];
                        data[ir * Wd + jr] = orig[ir][jr];
                    }
                }

                normalize_mean_dt(data.get(), datain.get(), Hd * Wd, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.);
#ifdef _OPENMP
                #pragma omp parallel for if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir += 1) {
                    for (int jr = 0; jr < Wd; jr += 1) {
                        tmpl->L[ir][jr] = data[ir * Wd + jr];
                    }
                }
            }

            if (lp.enaretiMask && lp.recothrr != 1.f) {
                float hig = lp.higthrr;
                float low = lp.lowthrr;
                float recoth = lp.recothrr;
                float decay = lp.decayr;
                bool invmask = false;
                maskrecov(tmpl, original, bufmaskorigreti, Hd, Wd, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
            }

            if (!lp.invret) {
                float minL = tmpl->L[0][0] - bufreti->L[0][0];
                float maxL = minL;
#ifdef _OPENMP
                #pragma omp parallel for reduction(min:minL) reduction(max:maxL) schedule(dynamic,16) if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir++) {
                    for (int jr = 0; jr < Wd; jr++) {
                        buflight[ir][jr] = tmpl->L[ir][jr] - bufreti->L[ir][jr];
                        minL = rtengine::min(minL, buflight[ir][jr]);
                        maxL = rtengine::max(maxL, buflight[ir][jr]);
                    }
                }

                float coef = 0.01f * rtengine::max(std::fabs(minL), std::fabs(maxL));

                if (coef > 0.f) {
                    coef = 1.f / coef;
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir++) {
                        for (int jr = 0; jr < Wd; jr++) {
                            buflight[ir][jr] *= coef;
                        }
                    }
                }

                transit_shapedetect_retinex(call, 4, bufreti, tmpl, bufmask, buforigmas, buflight, bufchro, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }
            }

            if (params->locallab.spots.at(sp).chrrt > 0) {
                if (!lp.invret && call == 2) {
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir += 1) {
                        for (int jr = 0; jr < Wd; jr += 1) {
                            orig[ir][jr] = std::sqrt(SQR(bufreti->a[ir][jr]) + SQR(bufreti->b[ir][jr]));
                            orig1[ir][jr] = std::sqrt(SQR(bufreti->a[ir][jr]) + SQR(bufreti->b[ir][jr]));
                        }
                    }
                }

                float maxChro = orig1[0][0];
#ifdef _OPENMP
                #pragma omp parallel for reduction(max:maxChro) schedule(dynamic,16) if (multiThread)
#endif

                for (int ir = 0; ir < Hd; ir++) {
                    for (int jr = 0; jr < Wd; jr++) {
                        maxChro = rtengine::max(maxChro, orig1[ir][jr]);
                    }
                }

                //first step change saturation without Retinex ==> gain of time and memory
                float satreal = lp.str * static_cast<float>(params->locallab.spots.at(sp).chrrt) / 100.f;

                if (params->locallab.spots.at(sp).chrrt <= 0.2) {
                    satreal /= 10.f;
                }

                DiagonalCurve reti_satur({
                    DCT_NURBS,
                    0, 0,
                    0.2, 0.2f + satreal / 250.f,
                    0.6, rtengine::min(1.f, 0.6f + satreal / 250.f),
                    1, 1
                });

                if (!lp.invret && call == 2) {

#ifdef _OPENMP
                    #pragma omp parallel for if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir += 1) {
                        for (int jr = 0; jr < Wd; jr += 1) {
                            const float Chprov = orig1[ir][jr];
                            float2 sincosval;
                            sincosval.y = Chprov == 0.0f ? 1.f : bufreti->a[ir][jr] / Chprov;
                            sincosval.x = Chprov == 0.0f ? 0.f : bufreti->b[ir][jr] / Chprov;

                            if (params->locallab.spots.at(sp).chrrt <= 40.0) { //first step
                                orig[ir][jr] = static_cast<float>(reti_satur.getVal(LIM01(orig[ir][jr] / maxChro))) * maxChro;
                            }

                            tmpl->a[ir][jr] = orig[ir][jr] * sincosval.y;
                            tmpl->b[ir][jr] = orig[ir][jr] * sincosval.x;
                        }
                    }

                    float minC = std::sqrt(SQR(tmpl->a[0][0]) + SQR(tmpl->b[0][0])) - orig1[0][0];
                    float maxC = minC;
#ifdef _OPENMP
                    #pragma omp parallel for reduction(min:minC) reduction(max:maxC) schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < Hd; ir++) {
                        for (int jr = 0; jr < Wd; jr++) {
                            bufchro[ir][jr] = std::sqrt(SQR(tmpl->a[ir][jr]) + SQR(tmpl->b[ir][jr])) - orig1[ir][jr];
                            minC = rtengine::min(minC, bufchro[ir][jr]);
                            maxC = rtengine::max(maxC, bufchro[ir][jr]);
                        }
                    }

                    float coefC = 0.01f * rtengine::max(std::fabs(minC), std::fabs(maxC));

                    if (coefC > 0.f) {
                        coefC = 1.f / coefC;
#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int ir = 0; ir < Hd; ir++) {
                            for (int jr = 0; jr < Wd; jr++) {
                                bufchro[ir][jr] *= coefC;
                            }
                        }
                    }
                }

                if (!lp.invret) {
                    transit_shapedetect_retinex(call, 5, tmpl, tmpl, bufmask, buforigmas, buflight, bufchro, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);

                    if (lp.recur) {
                        original->CopyFrom(transformed, multiThread);
                        float avge;
                        calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                    }
                }
            }

            delete tmpl;
            delete bufmask;
            delete bufmaskorigreti;

            if (!lp.enaretiMasktmap && lp.enaretiMask) {
                if (buforig) {
                    delete buforig;
                }

                if (buforigmas) {
                    delete buforigmas;
                }
            }

            delete  bufreti;
        }
    }


//begin cbdl
    if ((lp.mulloc[0] != 1.f || lp.mulloc[1] != 1.f || lp.mulloc[2] != 1.f || lp.mulloc[3] != 1.f || lp.mulloc[4] != 1.f || lp.mulloc[5] != 1.f || lp.clarityml != 0.f || lp.contresid != 0.f  || lp.enacbMask || lp.showmaskcbmet == 2 || lp.showmaskcbmet == 3 || lp.showmaskcbmet == 4 || lp.prevdE) && lp.cbdlena) {
        if (call <= 3) { //call from simpleprocess dcrop improcc
            const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
            const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
            const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
            const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
            int bfh = yend - ystart;
            int bfw = xend - xstart;

            if (bfw > 65 && bfh > 65) {
                array2D<float> bufsh(bfw, bfh);
                JaggedArray<float> bufchrom(bfw, bfh, true);
                const std::unique_ptr<LabImage> loctemp(new LabImage(bfw, bfh));
                const std::unique_ptr<LabImage> origcbdl(new LabImage(bfw, bfh));
                std::unique_ptr<LabImage> bufmaskorigcb;
                std::unique_ptr<LabImage> bufmaskblurcb;
                std::unique_ptr<LabImage> originalmaskcb;

                if (lp.showmaskcbmet == 2  || lp.enacbMask || lp.showmaskcbmet == 3 || lp.showmaskcbmet == 4) {
                    bufmaskorigcb.reset(new LabImage(bfw, bfh));
                    bufmaskblurcb.reset(new LabImage(bfw, bfh));
                    originalmaskcb.reset(new LabImage(bfw, bfh));
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = ystart; y < yend; y++) {
                    for (int x = xstart; x < xend; x++) {
                        loctemp->L[y - ystart][x - xstart] = original->L[y][x];
                    }
                }

                int inv = 0;
                bool showmaske = false;
                bool enaMask = false;
                bool deltaE = false;
                bool modmask = false;
                bool zero = false;
                bool modif = false;

                if (lp.showmaskcbmet == 3) {
                    showmaske = true;
                }

                if (lp.enacbMask) {
                    enaMask = true;
                }

                if (lp.showmaskcbmet == 4) {
                    deltaE = true;
                }

                if (lp.showmaskcbmet == 2) {
                    modmask = true;
                }

                if (lp.showmaskcbmet == 1) {
                    modif = true;
                }

                if (lp.showmaskcbmet == 0) {
                    zero = true;
                }

                float chrom = lp.chromacbm;;
                float rad = lp.radmacb;
                float gamma = lp.gammacb;
                float slope = lp.slomacb;
                float blendm = lp.blendmacb;
                float lap = params->locallab.spots.at(sp).lapmaskcb;
                bool pde = params->locallab.spots.at(sp).laplac;
                LocwavCurve dummy;
                int sco = params->locallab.spots.at(sp).scopemask;
                int lumask = params->locallab.spots.at(sp).lumask;
                int shado = 0;
                const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                float amountcd = 0.f;
                float anchorcd = 50.f;
                int shortcu = 0; //lp.mergemet; //params->locallab.spots.at(sp).shortc;
                LocHHmaskCurve lochhhmasCurve;
                const int highl = 0;
                maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, loctemp.get(), bufmaskorigcb.get(), originalmaskcb.get(), original, reserved, inv, lp,
                            0.f, false,
                            locccmascbCurve, lcmascbutili, locllmascbCurve, llmascbutili, lochhmascbCurve, lhmascbutili, lochhhmasCurve, false, multiThread,
                            enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskcblocalcurve, localmaskcbutili, dummy, false, 1, 1, 5, 5,
                            shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                            maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.0f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                           );

                if (lp.showmaskcbmet == 3) {
                    showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, loctemp.get(), transformed, bufmaskorigcb.get(), 0);

                    return;
                }

                constexpr float b_l = -5.f;
                constexpr float t_l = 25.f;
                constexpr float t_r = 120.f;
                constexpr float b_r = 170.f;
                constexpr double skinprot = 0.;
                int choice = 0;

                if (lp.showmaskcbmet == 0 || lp.showmaskcbmet == 1  || lp.showmaskcbmet == 2 || lp.showmaskcbmet == 4 || lp.enacbMask) {

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = ystart; y < yend; y++) {
                        for (int x = xstart; x < xend; x++) {
                            bufsh[y - ystart][x - xstart] = origcbdl->L[y - ystart][x - xstart] = original->L[y][x];
                            loctemp->a[y - ystart][x - xstart] = origcbdl->a[y - ystart][x - xstart] = original->a[y][x];
                            loctemp->b[y - ystart][x - xstart] = origcbdl->b[y - ystart][x - xstart] = original->b[y][x];
                            loctemp->L[y - ystart][x - xstart] = origcbdl->b[y - ystart][x - xstart] = original->L[y][x];
                        }
                    }

                    if (lp.clarityml != 0.f && lp.mulloc[5] == 1.f) { //enabled last level to retrieve level 5 and residual image in case user not select level 5
                        lp.mulloc[5] = 1.001f;
                    }

                    if (lp.contresid != 0.f && lp.mulloc[5] == 1.f) { //enabled last level to retrieve level 5 and residual image in case user not select level 5
                        lp.mulloc[5] = 1.001f;
                    }

                    ImProcFunctions::cbdl_local_temp(bufsh, loctemp->L, bfw, bfh, lp.mulloc, 1.f, lp.threshol, lp.clarityml, lp.contresid, skinprot, false, b_l, t_l, t_r, b_r, choice, sk, multiThread);


                    if (lp.softradiuscb > 0.f) {
                        softproc(origcbdl.get(), loctemp.get(), lp.softradiuscb, bfh, bfw, 0.001, 0.00001, 0.5f, sk, multiThread, 1);
                    }

                    if (lp.enacbMask && lp.recothrcb != 1.f) {
                        float hig = lp.higthrcb;
                        float low = lp.lowthrcb;
                        float recoth = lp.recothrcb;
                        float decay = lp.decaycb;
                        bool invmask = false;
                        maskrecov(loctemp.get(), original, bufmaskorigcb.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                    }

                }

                transit_shapedetect(6, loctemp.get(), originalmaskcb.get(), bufchrom, false, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, cx, cy, sk);

                const bool nochroma = (lp.showmaskcbmet == 2 || lp.showmaskcbmet == 1);

                //chroma CBDL begin here
                if (lp.chromacb > 0.f && !nochroma) {

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufsh[ir][jr] = std::sqrt(SQR(loctemp->a[ir][jr]) + SQR(loctemp->b[ir][jr]));
                        }
                    }

                    float multc[6];
                    float clarich = 0.5f * lp.clarityml;

                    if (clarich > 0.f && lp.mulloc[0] == 1.f) { //to enabled in case of user select only clarity
                        lp.mulloc[0] = 1.01f;
                    }

                    if (lp.contresid != 0.f && lp.mulloc[0] == 1.f) { //to enabled in case of user select only clarity
                        lp.mulloc[0] = 1.01f;
                    }

                    for (int lv = 0; lv < 6; lv++) {
                        multc[lv] = rtengine::max((lp.chromacb * (lp.mulloc[lv] - 1.f)) + 1.f, 0.01f);
                    }

                    choice = 1;
                    ImProcFunctions::cbdl_local_temp(bufsh, loctemp->L, bfw, bfh, multc, rtengine::max(lp.chromacb, 1.f), lp.threshol, clarich, 0.f, skinprot, false,  b_l, t_l, t_r, b_r, choice, sk, multiThread);

                    float minC = loctemp->L[0][0] - std::sqrt(SQR(loctemp->a[0][0]) + SQR(loctemp->b[0][0]));
                    float maxC = minC;
#ifdef _OPENMP
                    #pragma omp parallel for reduction(max:maxC) reduction(min:minC) schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufchrom[ir][jr] = (loctemp->L[ir][jr] - std::sqrt(SQR(loctemp->a[ir][jr]) + SQR(loctemp->b[ir][jr])));
                            minC = rtengine::min(minC, bufchrom[ir][jr]);
                            maxC = rtengine::max(maxC, bufchrom[ir][jr]);
                        }
                    }

                    float coefC = 0.01f * rtengine::max(std::fabs(minC), std::fabs(maxC));

                    if (coefC > 0.f) {
                        coefC = 1.f / coefC;
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++) {
                            for (int jr = 0; jr < bfw; jr++) {
                                bufchrom[ir][jr] *= coefC;
                            }
                        }
                    }

                    transit_shapedetect(7, loctemp.get(), nullptr, bufchrom, false, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, cx, cy, sk);
                }

                bufsh.free();

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }

            }
        }
    }


//end cbdl_Local

//vibrance
    float vibg = params->locallab.spots.at(sp).vibgam;

    if (lp.expvib && (lp.past != 0.f  || lp.satur != 0.f || lp.strvib != 0.f || vibg != 1.f  || lp.war != 0 || lp.strvibab != 0.f  || lp.strvibh != 0.f || lp.showmaskvibmet == 2 || lp.enavibMask || lp.showmaskvibmet == 3 || lp.showmaskvibmet == 4 || lp.prevdE) && lp.vibena) { //interior ellipse reinforced lightness and chroma  //locallutili
        if (call <= 3) { //simpleprocess, dcrop, improccoordinator
            const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
            const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
            const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
            const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
            const int bfh = yend - ystart;
            const int bfw = xend - xstart;

            if (bfw >= mSP && bfh >= mSP) {
                const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh));
                const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh));
                std::unique_ptr<LabImage> bufmaskorigvib;
                std::unique_ptr<LabImage> bufmaskblurvib;
                std::unique_ptr<LabImage> originalmaskvib;

                if (lp.showmaskvibmet == 2  || lp.enavibMask || lp.showmaskvibmet == 3 || lp.showmaskvibmet == 4) {
                    bufmaskorigvib.reset(new LabImage(bfw, bfh));
                    bufmaskblurvib.reset(new LabImage(bfw, bfh));
                    originalmaskvib.reset(new LabImage(bfw, bfh));
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                    }
                }

                int inv = 0;
                bool showmaske = false;
                bool enaMask = false;
                bool deltaE = false;
                bool modmask = false;
                bool zero = false;
                bool modif = false;

                if (lp.showmaskvibmet == 3) {
                    showmaske = true;
                }

                if (lp.enavibMask) {
                    enaMask = true;
                }

                if (lp.showmaskvibmet == 4) {
                    deltaE = true;
                }

                if (lp.showmaskvibmet == 2) {
                    modmask = true;
                }

                if (lp.showmaskvibmet == 1) {
                    modif = true;
                }

                if (lp.showmaskvibmet == 0) {
                    zero = true;
                }

                float chrom = lp.chromavib;
                float rad = lp.radmavib;
                float gamma = lp.gammavib;
                float slope = lp.slomavib;
                float blendm = lp.blendmavib;
                float lap = params->locallab.spots.at(sp).lapmaskvib;
                bool pde = params->locallab.spots.at(sp).laplac;
                LocwavCurve dummy;
                int sco = params->locallab.spots.at(sp).scopemask;
                int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;

                const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                int shado = 0;
                int lumask = params->locallab.spots.at(sp).lumask;
                LocHHmaskCurve lochhhmasCurve;
                float amountcd = 0.f;
                float anchorcd = 50.f;
                const int highl = 0;
                maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufexporig.get(), bufmaskorigvib.get(), originalmaskvib.get(), original, reserved, inv, lp,
                            0.f, false,
                            locccmasvibCurve, lcmasvibutili, locllmasvibCurve, llmasvibutili, lochhmasvibCurve, lhmasvibutili, lochhhmasCurve, false, multiThread,
                            enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskviblocalcurve, localmaskvibutili, dummy, false, 1, 1, 5, 5,
                            shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                            maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                           );

                if (lp.showmaskvibmet == 3) {
                    showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufexporig.get(), transformed, bufmaskorigvib.get(), 0);

                    return;
                }

                if (lp.showmaskvibmet == 0 || lp.showmaskvibmet == 1  || lp.showmaskvibmet == 2 || lp.showmaskvibmet == 4 || lp.enavibMask) {

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    /*
                    for (int y = ystart; y < yend; y++) {
                        for (int x = xstart; x < xend; x++) {
                            bufexporig->L[y - ystart][x - xstart] = original->L[y][x];
                            bufexporig->a[y - ystart][x - xstart] = original->a[y][x];
                            bufexporig->b[y - ystart][x - xstart] = original->b[y][x];
                        }
                    }
                    */
                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            // bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                            bufexporig->a[y][x] = original->a[y + ystart][x + xstart];
                            bufexporig->b[y][x] = original->b[y + ystart][x + xstart];
                        }
                    }

                    VibranceParams vibranceParams;
                    vibranceParams.enabled = params->locallab.spots.at(sp).expvibrance;
                    vibranceParams.pastels = params->locallab.spots.at(sp).pastels;
                    vibranceParams.saturated = params->locallab.spots.at(sp).saturated;
                    vibranceParams.psthreshold = params->locallab.spots.at(sp).psthreshold;
                    vibranceParams.protectskins = params->locallab.spots.at(sp).protectskins;
                    vibranceParams.avoidcolorshift = params->locallab.spots.at(sp).avoidcolorshift;
                    vibranceParams.pastsattog = params->locallab.spots.at(sp).pastsattog;
                    vibranceParams.skintonescurve = params->locallab.spots.at(sp).skintonescurve;


                    //    bufexpfin->CopyFrom(bufexporig.get(), multiThread);
                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            bufexpfin->L[y][x] = bufexporig->L[y][x];
                            bufexpfin->a[y][x] = bufexporig->a[y][x];
                            bufexpfin->b[y][x] = bufexporig->b[y][x];
                        }
                    }

                    if (lp.strvibh != 0.f) {
                        struct grad_params gph;
                        calclocalGradientParams(call, lp, gph, ystart, xstart, yend, xend,  bfw, bfh, oW, oH, tX, tY, tW, tH, 9, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                float factor = ImProcFunctions::calcGradientFactor(gph, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                float aa = bufexpfin->a[ir][jr];
                                float bb = bufexpfin->b[ir][jr];
                                float chrm = std::sqrt(SQR(aa) + SQR(bb));
                                float HH = xatan2f(bb, aa);

                                float newhr = 0.f;
                                float cor = 0.f;

                                if (factor < 1.f) {
                                    cor = - 2.5f * (1.f - factor);
                                } else if (factor > 1.f) {
                                    cor = 0.03f * (factor - 1.f);
                                }

                                newhr = HH + cor;

                                if (newhr > rtengine::RT_PI_F) {
                                    newhr -= 2 * rtengine::RT_PI_F;
                                } else if (newhr < -rtengine::RT_PI_F) {
                                    newhr += 2 * rtengine::RT_PI_F;
                                }

                                float2 sincosval = xsincosf(newhr);
                                bufexpfin->a[ir][jr] = clipC(chrm * sincosval.y);
                                bufexpfin->b[ir][jr] = clipC(chrm * sincosval.x);
                            }
                    }

                    if (lp.strvib != 0.f) {

                        struct grad_params gp;
                        calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 7, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++) {
                            for (int jr = 0; jr < bfw; jr++) {
                                bufexpfin->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk  + kbh*ir);
                            }
                        }
                    }

                    if (lp.strvibab != 0.f) {

                        struct grad_params gpab;
                        calclocalGradientParams(call, lp, gpab, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 8, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                const float factor = ImProcFunctions::calcGradientFactor(gpab, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                bufexpfin->a[ir][jr] *= factor;
                                bufexpfin->b[ir][jr] *= factor;
                            }
                    }

                    float gamma1 = params->locallab.spots.at(sp).vibgam;
                    rtengine::GammaValues g_a; //gamma parameters
                    double pwr1 = 1.0 / (double) gamma1;//default 3.0 - gamma Lab
                    double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                    rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

                    if (gamma1 != 1.f) {
#ifdef _OPENMP
                        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; ++y) {
                            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < bfw - 3; x += 4) {
                                STVFU(bufexpfin->L[y][x], F2V(32768.f) * igammalog(LVFU(bufexpfin->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                            }

#endif

                            for (; x < bfw; ++x) {
                                bufexpfin->L[y][x] = 32768.f * igammalog(bufexpfin->L[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                            }
                        }
                    }


                    ImProcFunctions::vibrance(bufexpfin.get(), vibranceParams, params->toneCurve.hrenabled, params->icm.workingProfile);

                    // float gamma =  params->locallab.spots.at(sp).vibgam;
                    //  rtengine::GammaValues g_a; //gamma parameters
                    // double pwr = 1.0 / (double) gamma;//default 3.0 - gamma Lab
                    // double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                    // rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

                    if (gamma1 != 1.f) {
#ifdef _OPENMP
                        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < bfw - 3; x += 4) {
                                STVFU(bufexpfin->L[y][x], F2V(32768.f) * gammalog(LVFU(bufexpfin->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                            }

#endif

                            for (; x < bfw; ++x) {
                                bufexpfin->L[y][x] = 32768.f * gammalog(bufexpfin->L[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                            }
                        }
                    }


                    if (params->locallab.spots.at(sp).warm != 0) {
                        bool HHcurvejz = false, CHcurvejz = false, LHcurvejz = false;

                        ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 2, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                    }

                    if (lp.enavibMask && lp.recothrv != 1.f) {
                        float recoth = lp.recothrv;

                        if (lp.recothrv < 1.f) {
                            recoth = -1.f * recoth + 2.f;
                        }

                        float hig = lp.higthrv;
                        float low = lp.lowthrv;
                        //  float recoth = lp.recothrv;
                        float decay = lp.decayv;
                        bool invmask = false;
                        maskrecov(bufexpfin.get(), original, bufmaskorigvib.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                    }

                    if (lp.recothrv >= 1.f) {
                        transit_shapedetect2(sp, 0.f, 0.f, call, 2, bufexporig.get(), bufexpfin.get(), originalmaskvib.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                    } else {
                        transit_shapedetect2(sp, 0.f, 0.f, call, 2, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);

                    }

                }


                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }
            }
        }
    }


//shadow highlight
    bool tonequ = false;

    if (lp.mullocsh[0] != 0 || lp.mullocsh[1] != 0 || lp.mullocsh[2] != 0 || lp.mullocsh[3] != 0 || lp.mullocsh[4] != 0 || lp.mullocsh[5] != 0) {
        tonequ = true;
    }

    bool tonecurv = false;
    const Glib::ustring profile = params->icm.workingProfile;
    bool isworking = (profile == "sRGB" || profile == "Adobe RGB" || profile == "ProPhoto" || profile == "WideGamut" || profile == "BruceRGB" || profile == "Beta RGB" || profile == "BestRGB" || profile == "Rec2020" || profile == "ACESp0" || profile == "ACESp1");

    if (isworking && (params->locallab.spots.at(sp).gamSH != 2.4 || params->locallab.spots.at(sp).sloSH != 12.92)) {
        tonecurv = true;
    }

    bool ghsactiv = false;
    float D = params->locallab.spots.at(sp).ghs_D;//enable GHS and Stretch factor
    //float BLP = params->locallab.spots.at(sp).ghs_BLP;
    //float HLP = params->locallab.spots.at(sp).ghs_HLP;
    bool smoth = params->locallab.spots.at(sp).ghs_smooth;//Highlight attenuation
    float MID = params->locallab.spots.at(sp).ghs_MID;//midtones

    if (D != 0.f /* || BLP != 0.f || HLP != 1.f*  || smoth*/) {
        ghsactiv = true;
    }
    if (! lp.invsh && (lp.highlihs > 0.f || lp.shadowhs > 0.f || tonequ || tonecurv || ghsactiv || lp.strSH != 0.f || lp.showmaskSHmet == 2 || lp.enaSHMask || lp.showmaskSHmet == 3 || lp.showmaskSHmet == 4 || lp.prevdE) && call <= 3 && lp.hsena) {
        const int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        const int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        const int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        const int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        const int bfh = yend - ystart;
        const int bfw = xend - xstart;

        if (bfw >= mSP && bfh >= mSP) {
            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh));
            std::unique_ptr<LabImage> bufmaskorigSH;
            std::unique_ptr<LabImage> bufmaskblurSH;
            std::unique_ptr<LabImage> originalmaskSH;
            const std::unique_ptr<LabImage> tmp1(new LabImage(transformed->W, transformed->H));// to use for plain image

            if (lp.showmaskSHmet == 2  || lp.enaSHMask || lp.showmaskSHmet == 3 || lp.showmaskSHmet == 4) {
                bufmaskorigSH.reset(new LabImage(bfw, bfh));
                bufmaskblurSH.reset(new LabImage(bfw, bfh));
                originalmaskSH.reset(new LabImage(bfw, bfh));
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bfh; y++) {
                for (int x = 0; x < bfw; x++) {
                    bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                }
            }

            int inv = 0;
            bool showmaske = false;
            bool enaMask = false;
            bool deltaE = false;
            bool modmask = false;
            bool zero = false;
            bool modif = false;

            if (lp.showmaskSHmet == 3) {
                showmaske = true;
            }

            if (lp.enaSHMask) {
                enaMask = true;
            }

            if (lp.showmaskSHmet == 4) {
                deltaE = true;
            }

            if (lp.showmaskSHmet == 2) {
                modmask = true;
            }

            if (lp.showmaskSHmet == 1) {
                modif = true;
            }

            if (lp.showmaskSHmet == 0) {
                zero = true;
            }

            float chrom = lp.chromaSH;
            float rad = lp.radmaSH;
            float gamma = lp.gammaSH;
            float slope = lp.slomaSH;
            float blendm = lp.blendmaSH;
            float lap = params->locallab.spots.at(sp).lapmaskSH;
            bool pde = params->locallab.spots.at(sp).laplac;
            LocwavCurve dummy;
            int sco = params->locallab.spots.at(sp).scopemask;
            int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;

            const float mindE = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            int shado = 0;
            float amountcd = params->locallab.spots.at(sp).fatamountSH;
            float anchorcd = params->locallab.spots.at(sp).fatanchorSH;
            int lumask = params->locallab.spots.at(sp).lumask;
            LocHHmaskCurve lochhhmasCurve;
            const int highl = 0;
            maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufexporig.get(), bufmaskorigSH.get(), originalmaskSH.get(), original, reserved, inv, lp,
                        0.f, false,
                        locccmasSHCurve, lcmasSHutili, locllmasSHCurve, llmasSHutili, lochhmasSHCurve, lhmasSHutili, lochhhmasCurve, false, multiThread,
                        enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskSHlocalcurve, localmaskSHutili, dummy, false, 1, 1, 5, 5,
                        shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                        maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                       );

            if (lp.showmaskSHmet == 3) {
                showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufexporig.get(), transformed, bufmaskorigSH.get(), 0);

                return;
            }
            //to test gradiant in mode plain image GW GH instead of bfw bfh - I prefer to keep this code, even if it is not used, because it is another way to approach the graduated Filter in SE
            LocalLabGradientMode grad = LocalLabGradientMode::STANDARD;// grad = 1 to plain image GF
            int ca1 = 1;//dcrop
            int ca2 = 2;//simpleprocess
            int ca3 = 3;//improccordinator
            int ca4 = 10;//do nothing
            if(grad == LocalLabGradientMode::PLAIN_IMAGE) {//plain image GF
                ca1 = 2;
                ca2 = 2;
                ca3 = 3;
                ca4 = 1;
            }
            
            bool execgradsh = false;
            if(lp.fullim == 3 || lp.fullim == 2 || lp.fullim == 0) {
                execgradsh = true;
            }

            if (lp.showmaskSHmet == 0 || lp.showmaskSHmet == 1  || lp.showmaskSHmet == 2 || lp.showmaskSHmet == 4 || lp.enaSHMask) {

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                        bufexporig->a[y][x] = original->a[y + ystart][x + xstart];
                        bufexporig->b[y][x] = original->b[y + ystart][x + xstart];
                        bufexpfin->L[y][x] = original->L[y + ystart][x + xstart];
                        bufexpfin->a[y][x] = original->a[y + ystart][x + xstart];
                        bufexpfin->b[y][x] = original->b[y + ystart][x + xstart];
                    }
                }

                if (lp.shmeth == 0) {
                    ImProcFunctions::shadowsHighlights(bufexpfin.get(), lp.hsena, 1, lp.highlihs, lp.shadowhs, lp.radiushs, sk, lp.hltonalhs, lp.shtonalhs);
                }

                if (lp.shmeth == 1) {
                    double scal = (double)(sk);
                    Imagefloat *tmpImage = nullptr;
                    tmpImage = new Imagefloat(bfw, bfh);
                    lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile);
                    Glib::ustring prof = params->icm.workingProfile;

                    if (tonecurv) { //Tone response curve  : does nothing if gamma=2.4 and slope=12.92 ==> gamma sRGB
                        float gamtone = params->locallab.spots.at(sp).gamSH;
                        float slotone = params->locallab.spots.at(sp).sloSH;
                        cmsHTRANSFORM dummy = nullptr;
                        int ill = 0;
                        int locprim = 0;
                        float rdx, rdy, grx, gry, blx, bly = 0.f;
                        float meanx, meany, meanxe, meanye, maxdat = 0.f;
                        double p[6] = {0., 0., 0., 0., 0., 0.};

                        workingtrc(0, tmpImage, tmpImage, bfw, bfh, -5, prof, 2.4, 12.92310, 0, ill, 0, 0, rdx, rdy, grx, gry, blx, bly , meanx, meany, meanxe, meanye, maxdat, p, dummy, true, false, false, false);
                        workingtrc(0, tmpImage, tmpImage, bfw, bfh, 1, prof, gamtone, slotone, 0, ill, 0, locprim, rdx, rdy, grx, gry, blx, bly , meanx, meany, meanxe, meanye, maxdat, p, dummy, false, true, true, false);//be careful no gamut control
                    }

                    if (tonequ) {
                        tone_eq(this, tmpImage, lp, params->icm.workingProfile, scal, multiThread);
                    }

                    rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile);

                    delete tmpImage;
                }
                
                if (lp.shmeth == 2) {//GHS 2024 - 2026
                    if (ghsactiv) {
                        // GHT filter ported from Siril - help with ART CTL thanks to Alberto Griggio
                        TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);

                        float B = params->locallab.spots.at(sp).ghs_B;//Local intensity 
                        float LP = params->locallab.spots.at(sp).ghs_LP;//Protect shadows
                        float SP = params->locallab.spots.at(sp).ghs_SP;//Symmetry point
                        float HP = params->locallab.spots.at(sp).ghs_HP;//Protect highlights
                        /*
                        int blackpoint = 100. * params->locallab.spots.at(sp).ghs_BLP;//Black point
                        float shiftblackpoint = params->locallab.spots.at(sp).ghs_BLP;//Black point
                        float shiftwhitepoint = params->locallab.spots.at(sp).ghs_HLP;//White point
                        */
                        if (LP > SP) {
                            LP = SP;
                        }
                        if (HP < SP) {
                            HP = SP;
                        }
                        //ghshp2 = HP;
                        bool ghsinv = params->locallab.spots.at(sp).ghs_inv;//Inverse stretch
                        int met = 0;
                        GHTStrType strtype = GHTStrType::NORMAL;
                        if (ghsinv) {
                            strtype = GHTStrType::INVERSE;
                        } else {
                            strtype = GHTStrType::NORMAL;
                        }
                        if (params->locallab.spots.at(sp).ghsMethod == "rgb") {//mode RGB default in luminance mode
                            met = 0;
                        } else if (params->locallab.spots.at(sp).ghsMethod == "rgbstd") {//mode RGB standard
                            met = 1;
                        } else if (params->locallab.spots.at(sp).ghsMethod == "llab") {//Mode Lab
                            met = 2;
                        } else if (params->locallab.spots.at(sp).ghsMethod == "lum") {// L hsl
                            met = 3;
                        } else if (params->locallab.spots.at(sp).ghsMethod == "sat") {// sat hsl
                            met = 4;
                        } else if (params->locallab.spots.at(sp).ghsMethod == "hue") {// hue hsl
                            met = 5;
                        }
                        bool ghsautoSP = params->locallab.spots.at(sp).SPAutoRadius;

                        const ght_compute_params c = GHT_setup(B, D, LP, SP, HP, strtype);//setup system with entries
                        constexpr float epsilg = 0.00001f;

                        std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));
                        lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile);
                        Glib::ustring prof = params->icm.workingProfile;
                        float ghsslop = params->locallab.spots.at(sp).ghs_slope;
                        float ghschro = params->locallab.spots.at(sp).ghs_chro;
                        rtengine::GammaValues g_a; //gamma parameters
                        float gamma1 = 3.0f; //params->locallab.spots.at(sp).shargam;

                        double pwr1 = 1.0 / (double) 3.0;//default 3.0 - gamma Lab
                        double ts1 = ghsslop;//always the same 'slope' in the extreme shadows - slope Lab
                        rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope
                        const float noise = pow_F(2.f, -16.f);//GHS - do not process very low values which are probably noise.
                        float minb = 100.f;
                        float maxw = -100.f;
                        float maxwred = -100.f;
                        float maxwgreen = -100.f;
                        float maxwblue = -100.f;
                        constexpr float range = 65535.f;
                        using Triple = std::array<double, 3>;
                        using Matrix = std::array<Triple, 3>;

                        Matrix inv_lms_T = {};//initialize inv_lms_T
                        Matrix lms_mat = {};//initialize lms_mat
                        TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile);
                        //inverse matrix user select
                        const float wip[3][3] = {
                            {static_cast<float>(wiprof[0][0]), static_cast<float>(wiprof[0][1]), static_cast<float>(wiprof[0][2])},
                            {static_cast<float>(wiprof[1][0]), static_cast<float>(wiprof[1][1]), static_cast<float>(wiprof[1][2])},
                            {static_cast<float>(wiprof[2][0]), static_cast<float>(wiprof[2][1]), static_cast<float>(wiprof[2][2])}
                        };
                        TMatrix wprofi = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
                        const float wpi[3][3] = {
                        {static_cast<float>(wprofi[0][0]), static_cast<float>(wprofi[0][1]), static_cast<float>(wprofi[0][2])},
                        {static_cast<float>(wprofi[1][0]), static_cast<float>(wprofi[1][1]), static_cast<float>(wprofi[1][2])},
                        {static_cast<float>(wprofi[2][0]), static_cast<float>(wprofi[2][1]), static_cast<float>(wprofi[2][2])}
                        };
                        const bool isrgb = params->locallab.spots.at(sp).ghsMatmet == "JZ" || params->locallab.spots.at(sp).ghsMatmet == "agx" || params->locallab.spots.at(sp).ghsMatmet == "cat16";
                        //isrgb - when the user chooses the RGB mode which introduces a cognitive bias.

                        if (params->locallab.spots.at(sp).ghsMatmet != "none") {
                            if (params->locallab.spots.at(sp).ghsMatmet == "agx") {// for Rec2020 with chromatic adaptation D50 from Sobotka AgX-Resolve (origin uncertain).
                                //It's very unusual to apply this transformation in RGB space rather than XYZ space, but why not, especially since we're only affecting the differences
                                //caused by the change and often we're dealing with a high White Point, therefore outside the usual XYZ values.

                                //Define AgX matrix for color space transformation
                                lms_mat = {{//AgX
                                    { 0.856627153315983, 0.0951212405381588, 0.0482516061458583 },
                                    { 0.137318972929847, 0.761241990602591, 0.101439036467562 },
                                    { 0.11189821299995, 0.0767994186031903, 0.811302368396859 }
                                }};
                            } else if (params->locallab.spots.at(sp).ghsMatmet == "JZ"  || params->locallab.spots.at(sp).ghsMatmet == "JZxyz") { //original LMS JzAzBz matrix without PQ, whitout Absolute luminance, whitout "az and bz"
                                lms_mat = {{//JzAzBz - Actually, it's not the JzAzBz model but an approximate cognitive bias in RGB mode. No bias in XYZ.
                                    { 0.41478972, 0.579999, 0.0146480 },
                                    { -0.2015100, 1.120649, 0.0531008 },
                                    { -0.0166008, 0.264800, 0.6684799 }
                                }};
                            } else if (params->locallab.spots.at(sp).ghsMatmet == "cat16" || params->locallab.spots.at(sp).ghsMatmet == "cat16xyz") { //original 'LMS Cat16' matrix, of course without CIECAM treatment.
                                lms_mat = {{//Cat16 - Actually, it's not the Cat16 model but an approximate cognitive bias in RGB mode, but less biased than JzAzBz. No bias in XYZ.
                                    { 0.44113111, 0.46084198975, 0.090051211104 },
                                    { 0.176890718, 0.724815611, 0.06249008 },
                                    { 0.061414342, 0.196120268, 0.5430087122 }
                                }};
                            }

                            Matrix lms_T = {};
                            Color::transpose(lms_mat, lms_T);//transpose Matrix
                            //invert matrix
                            if (!rtengine::invertMatrix(lms_T, inv_lms_T)) {
                                if (settings->verbose) {
                                    std::cout << "Matrix is not invertible, skipping and use this one" << std::endl;
                                }
                                //If the calculations fail, we use this matrix calculated with a spreadsheet. Note that if 'lms_mat' changes, you must redo the calculations.
                                if (params->locallab.spots.at(sp).ghsMatmet == "agx") {
                                    inv_lms_T[0][0] = 1.1974410768877;
                                    inv_lms_T[0][1] = -0.196474626321346;
                                    inv_lms_T[0][2] = -0.146557417106601;
                                    inv_lms_T[1][0] = -0.144261512698001;
                                    inv_lms_T[1][1] = 1.35409513146973;
                                    inv_lms_T[1][2] = -0.1082844058788469;
                                    inv_lms_T[2][0] = -0.0531795641897042;
                                    inv_lms_T[2][1] = -0.157620505148385;
                                    inv_lms_T[2][2] = 1.25484147589507;
                                } else if (params->locallab.spots.at(sp).ghsMatmet == "JZ" || params->locallab.spots.at(sp).ghsMatmet == "JZxyz") {////I chose JZ rather than JzAzBz because we're dealing with a cognitive bias in RGB mode, not in XYZ. This is only the use of the matrice and not the JzAzBz algorithm.
                                    inv_lms_T[0][0] = 1.92488743175646;
                                    inv_lms_T[0][1] = 0.349838855125251;
                                    inv_lms_T[0][2] = -0.097770847478916;
                                    inv_lms_T[1][0] = -1.00252032146704;
                                    inv_lms_T[1][1] = 0.72483850737164;
                                    inv_lms_T[1][2] = -0.312021163395669;
                                    inv_lms_T[2][0] = 0.0265884071012174;
                                    inv_lms_T[2][1] = -0.0573856961296308;
                                    inv_lms_T[2][2] = 1.51932335013174;
                                } else if (params->locallab.spots.at(sp).ghsMatmet == "cat16" || params->locallab.spots.at(sp).ghsMatmet == "cat16xyz") {//I chose cat16 rather than Cat16 because we're dealing with a cognitive bias in RGB mode, not in XYZ. This is only the use of the matrice and not the CAM16 algorithm.
                                    inv_lms_T[0][0] = 3.05467729554555;
                                    inv_lms_T[0][1] = -0.738708117076132;
                                    inv_lms_T[0][2] = -0.0786826459200281;
                                    inv_lms_T[1][0] = -1.86312660313314;
                                    inv_lms_T[1][1] = 1.87455986414727;
                                    inv_lms_T[1][2] = -0.46632122626262804;
                                    inv_lms_T[2][0] = -0.29216927086322;
                                    inv_lms_T[2][1] = -0.0932210370533556;
                                    inv_lms_T[2][2] = 1.90830440656202;
                                }
                            }
                            //now we have 3 Matrices to convert tmpimage with Agx, JzAzBz in RGB (JZ ), Cat16 in RGB (cat16), JzAzBz in XYZ (JZxyz ), Cat16 in XYZ (cat16xyz)

                            if (isrgb ) {//mode RGB for Cat16 and JZ, and also Agx

#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                                for (int i = 0; i < bfh; ++i)
                                    for (int j = 0; j < bfw; ++j) {
                                        const float r = tmpImage->r(i, j);
                                        const float g = tmpImage->g(i, j);
                                        const float b = tmpImage->b(i, j);
                                        std::array<float, 3> rgb_in{r, g, b};
                                        float rout = 0.f;
                                        float gout = 0.f;
                                        float bout = 0.f;
                                        Color::agx_trans(rgb_in, lms_T, rout, gout, bout);
                                        tmpImage->r(i, j) = rtengine::max(epsilg, rout);//avoid negative values. Normally this should never happen because the coefficients of the selected matrix are all positive... unless the matrix changes
                                        tmpImage->g(i, j) = rtengine::max(epsilg, gout);//these potentially negative values, related to calculations and not to the gamut, are not accepted by the rgblab or labrgb, workingtrc functions, etc,
                                        tmpImage->b(i, j) = rtengine::max(epsilg, bout);//but after numerous checks, this has no impact on the results...except to prevent a crash.
                                    }
                            } else {//cat16xyz and JZxyz
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int i = 0; i < bfh; ++i)
                                    for (int j = 0; j < bfw; ++j) {
                                        const float r = tmpImage->r(i, j);
                                        const float g = tmpImage->g(i, j);
                                        const float b = tmpImage->b(i, j);
                                        float X, Y, Z;
                                        Color::rgbxyz(r, g, b, X, Y, Z, wpi);//convert to XYZ using the working profile
                                        std::array<float, 3> xyz_in{X, Y, Z};
                                        float Xout = 0.f;
                                        float Yout = 0.f;
                                        float Zout = 0.f;
                                        Color::agx_trans(xyz_in, lms_T, Xout, Yout, Zout);//multiplies the XYZ data with the conversion matrice Cat16 or JZ
                                        float rout = 0.f;
                                        float gout = 0.f;
                                        float bout = 0.f;
                                        Color::xyz2rgb(Xout, Yout, Zout, rout, gout, bout, wip);//convert to RGB using inverse working profile.
                                        tmpImage->r(i, j) = rtengine::max(epsilg, rout);//avoid negative values. Normally this should never happen because the coefficients of the selected matrix are all positive... unless the matrix changes
                                        tmpImage->g(i, j) = rtengine::max(epsilg, gout);//these potentially negative values, related to calculations and not to the gamut, are not accepted by the rgblab or labrgb, workingtrc functions, etc,
                                        tmpImage->b(i, j) = rtengine::max(epsilg, bout);//but after numerous checks, this has no impact on the results...except to prevent a crash.
                                    }
                            }
    
    
                        }

                        const bool autobw = params->locallab.spots.at(sp).ghs_autobw;
                        if (autobw == true  && strtype == GHTStrType::NORMAL) { //find probably White point and black point ...Must be adjusted manually in some cases notably Black point with negatives values...                        
#ifdef _OPENMP
        #   pragma omp parallel for reduction(min:minb) reduction(max:maxw) reduction(max:maxwred) reduction(max:maxwgreen) reduction(max:maxwblue) if (multiThread)
#endif
                             for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float r = tmpImage->r(i, j) / range;
                                    float g = tmpImage->g(i, j) / range;
                                    float b = tmpImage->b(i, j) / range;
                                    float minrgb = rtengine::min(r, g, b);
                                    if (minrgb < minb){
                                        minb = minrgb;
                                    }
                                 
                                    float maxrgb = rtengine::max(r, g, b);
                                    if (maxrgb > maxw){
                                        maxw = maxrgb;
                                    }
                                    float maxr = r;
                                    if (maxr > maxwred){
                                        maxwred = maxr;
                                    }
                                    float maxg = g;
                                    if (maxg > maxwgreen){
                                        maxwgreen = maxg;
                                    }
                                    float maxb = b;
                                    if (maxb > maxwblue){
                                        maxwblue = maxb;
                                    }
                                 }
                                const float noise = pow_F(2.f, -16.f);
                                minb = rtengine::max(minb, noise);//set a very minimal value in all cases to avoid 0
                                ghsbwslider[1]= maxw;
                                ghsbwslider[0]= minb; 
                                ghscolor[0] = maxwred; 
                                ghscolor[1] = maxwgreen;
                                ghscolor[2] = maxwblue;  
                                const float log2 = std::log(2.f);
                                const float DRghs = -xlogf(minb / maxw) / log2; //calculate dynamic Range GHS with max and min absolute values, and not with luminance
                                ghscolor[3] = DRghs; 
                        }
          
                        int blackpoint = 100. * params->locallab.spots.at(sp).ghs_BLP;//Black point
                        float shiftblackpoint = params->locallab.spots.at(sp).ghs_BLP;//Black point
                        float shiftwhitepoint = params->locallab.spots.at(sp).ghs_HLP;//White point
                        constexpr float low_limit_white_point = 0.9f; //reasonable limit where we can consider that the highlights are low (at least white point < 1).
                        //This occurs when the limits of the highlights are not reached, then white point low.
                        constexpr float reasonable_limit_white_point = 4.2f; //reasonable limit where we can consider that the highlights are very high
                        //This occurs either when 'Highlight reconstruction' is not activated or when the value recovered with reconstruction is quite low. 
                        //This is the majority of cases. In this case, I apply 'norm2', which combines the estimated XYZ Luminance values ​​with out-of-gamut values ​​at 50%.
                        //In other cases, sunsets, images with LEDs,etc. the WP linear values ​​can be very high, up to 11... I vary the ratio from 50% up to 85% for out-of-gamut lights.
                        //But all of this is quite empirical, based on trials/experiments and not sophisticated mathematical formulas (like almost all colorimetry...)

                        if (shiftblackpoint < 0.f && strtype == GHTStrType::NORMAL) {//change only Black point with negatives values for in some cases out of gamut values
                            //rgb value can be very weakly negatives (eg working space sRGB in some rare cases) - tone_eqblack prevents it
                            //also change black value to help "ghs" and avoid noise
                            tone_eqblack(this, tmpImage.get(), -5 * blackpoint, params->icm.workingProfile, sk, multiThread);//Ev -16 to -8
                                                        // -5 to be in range 0..100
                        }
                        {//change black point and white point for GHS
                         // Sets the Blackpoint and Whitepoint for a linear stretch of the image
                            float shiftblackpoint2 = shiftblackpoint;
                            if (shiftblackpoint < 0.f  && strtype == GHTStrType::NORMAL) {
                                shiftblackpoint2 = 0.f;//set to zero if  black point negatif, no change 
                            } 
                            if (strtype == GHTStrType::INVERSE) {
                                shiftblackpoint2 = shiftblackpoint;
                            }

                            int bpnb = 0;
                            int wpnb = 0;
                            float minbp = 1.f;
                            float maxwp = 0.f;

                            t1.set();

#ifdef _OPENMP
        #   pragma omp parallel for reduction(+:bpnb, wpnb) reduction(min:minbp) reduction(max:maxwp) if (multiThread)  //for schedule(dynamic,16)
#endif
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float r = tmpImage->r(i, j) / range;
                                    float g = tmpImage->g(i, j) / range;
                                    float b = tmpImage->b(i, j) / range;
                                    float Ro, Go, Bo;
                                    float deltawp = rtengine::max(0.05f, shiftwhitepoint - shiftblackpoint2);//0.05 minimum acceptable
                                    if (strtype == GHTStrType::NORMAL) {
                                        Ro = (r - shiftblackpoint2) / deltawp;
                                        Go = (g - shiftblackpoint2) / deltawp;
                                        Bo = (b - shiftblackpoint2) / deltawp;
                                    } else {
                                        Ro = (shiftblackpoint2) + r * deltawp;
                                        Go = (shiftblackpoint2) + g * deltawp;
                                        Bo = (shiftblackpoint2) + b * deltawp;
                                    }
                                    float minrgb = rtengine::min(Ro, Go, Bo);
                                    if (minrgb < minbp){
                                        minbp = minrgb;
                                    }
          
                                    float maxrgb = rtengine::max(Ro, Go, Bo);
                                    if (maxrgb > maxwp){
                                        maxwp = maxrgb;
                                    }
          
                                    if (Ro < 0.f || Go < 0.f || Bo < 0.f) {
                                        bpnb++;
                                    }
                                    if (Ro > 1.f || Go > 1.f || Bo > 1.f) {
                                        wpnb++;
                                    }
                                    if (strtype == GHTStrType::NORMAL ) { //strtype == GHTStrType::NORMAL only strtype == GHTStrType::NORMAL if crash
                                        tmpImage->r(i, j) = rtengine::max(epsilg, Ro * range);//epsilg = 0.00001f to avoid crash
                                        tmpImage->g(i, j) = rtengine::max(epsilg, Go * range);
                                        tmpImage->b(i, j) = rtengine::max(epsilg, Bo * range);
                                    }  else if (strtype == GHTStrType::INVERSE) {//to uncomment if crash
                                        tmpImage->r(i, j) = clipR(rtengine::max(epsilg, Ro * range));//epsil 0.0001f to avoid crash different from 'normal'
                                        tmpImage->g(i, j) = clipR(rtengine::max(epsilg, Go * range));//clipR to avoid crash in some cases
                                        tmpImage->b(i, j) = clipR(rtengine::max(epsilg, Bo * range));
                                    } 
                                }
          
                                //evaluation symmetry point only in RGB mode    
                                LUTu symhist(65535);
                                symhist.clear();
                                array2D<float> Y2(bfw, bfh);
                                //generate histogram RGB with norm, norm2 or norm_3 equivalent luminance
                                for (int i = 0; i < bfh; ++i)
                                    for (int j = 0; j < bfw; ++j) {
                                        if (shiftwhitepoint < low_limit_white_point) {//comparison between the calculate WP and low_limit_white_point (low white point)
                                            Y2[i][j] = norm(clipR(tmpImage->r(i, j)), clipR(tmpImage->g(i, j)), clipR(tmpImage->b(i, j)), wprof);//clipR to avoid bad data in histogram - This is not a precise calculation but an assessment
                                        } else if (shiftwhitepoint < reasonable_limit_white_point) { //comparison between the calculated WP and the reasonable limit
                                            Y2[i][j] = norm2(clipR(tmpImage->r(i, j)), clipR(tmpImage->g(i, j)), clipR(tmpImage->b(i, j)), wprof);//clipR to avoid bad data in histogram - This is not a precise calculation but an assessment
                                        } else {
                                            Y2[i][j] = norm_3(clipR(tmpImage->r(i, j)), clipR(tmpImage->g(i, j)), clipR(tmpImage->b(i, j)), wprof, shiftwhitepoint / reasonable_limit_white_point);//clipR to avoid bad data in histogram - This is not a precise calculation but an assessment
                                        }
                                        int pos = (int) Y2[i][j];
                                        symhist[pos]++;
                                    }
                                //find maximum number - pic luminance
                                unsigned int maxhist = 0;
                                int kk = 0;
                                float symref = 0.f;
                                int symmaxvalue = 55000;//Limits (aribitrary) for calculation the maximum value to be scanned in the histogram, as it is probably erroneous, especially with LEDs.
                                for (int j=0; j < symmaxvalue; ++j) {
                                    if (symhist[j] > maxhist) {
                                        maxhist =  symhist[j];
                                        kk = j;
                                        symref = (float)kk / 65535.f;
                                    }
                                }


                                ghsbpwp[0] = bpnb;
                                ghsbpwp[1] = wpnb;
                                ghsbpwpvalue[0] = minbp;
                                ghsbpwpvalue[1] = maxwp;
                                ghssym = symref;
                                if ((met == 0  || met == 1)  && ghsautoSP) {//RGB mode and auto Symmetry point
                                    ghsautsp = true;
                                } else {
                                    ghsautsp = false;
                                }
                                t2.set();
                                if (settings->verbose) {
                                    printf("Values: maxhist=%i kk=%i symref=%f\n", maxhist, kk, (double) ghssym);
                                    printf("calculate Black Point and White Point: %d nsec\n",  t2.etime(t1));
                                }


                        }
                        if (met == 0  || met == 1) {//RGB mode
                            const auto sf =
                                [=](float s, float c) -> float
                                {
                                    if (c > noise) {
                                        return 1.f - min(std::abs(s) / c, 1.f);
                                    } else {
                                        return 0.f;
                                    }
                                };
                            //saturation
                            const auto apply_sat =
                                [&](float &r, float &g, float &b, float f, float ll) -> void
                                {
                                    float rl = r - ll;
                                    float gl = g - ll;
                                    float bl = b - ll;
                                    // The parameters 0.2f, 0.6f, and 0.4f control the nonlinearity and scaling of the saturation adjustment:
                                    // - 0.2f: exponent for the power function, affecting the response curve of the adjustment factor.
                                    // - 0.6f: scaling factor for the powered value.
                                    // - 0.4f: base offset added to ensure a minimum effect.
                                    // Adjust these values to fine-tune the strength and shape of the local saturation effect.
                                    float s = intp(max(sf(rl, r), sf(gl, g), sf(bl, b)), pow_F(f, 0.2f) * 0.6f + 0.4f, 1.f);
                                    r = ll + s * rl;
                                    g = ll + s * gl;
                                    b = ll + s * bl;
                                };

                            //local contrast with guidedfilter incorporated in RGB luminance met = 0
                            array2D<float> Yc(bfw, bfh);
                                {
                                    constexpr float base_posterization = 20.f;
                                    array2D<float> Y2(bfw, bfh);

#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif
                                    for (int y = 0; y < bfh; ++y) {
                                        for (int x = 0; x < bfw; ++x) {
                                            if (shiftwhitepoint < low_limit_white_point) {//comparison between the calculate WP and low_limit_white_point (low white point)
                                                Y2[y][x] = norm(tmpImage->r(y, x), tmpImage->g(y, x), tmpImage->b(y, x), wprof) / 65535.f;//norm
                                            } else if (shiftwhitepoint < reasonable_limit_white_point) {//comparison between the calculated WP and the reasonable limit
                                                Y2[y][x] = norm2(tmpImage->r(y, x), tmpImage->g(y, x), tmpImage->b(y, x), wprof) / 65535.f;//norm2
                                            } else {
                                                Y2[y][x] = norm_3(tmpImage->r(y, x), tmpImage->g(y, x), tmpImage->b(y, x), wprof, ghsbpwpvalue[1] / reasonable_limit_white_point) / 65535.f;//norm_3
                                            }
                                            float l = xlogf(rtengine::max(Y2[y][x], 1e-9f));
                                            float ll = round(l * base_posterization) / base_posterization;
                                            Yc[y][x] = xexpf(ll);
                                            assert(std::isfinite(Yc[y][x]));
                                        }
                                    }

                                    const float radius = rtengine::max(bfw, bfh) / 30.f;
                                    const float epsilon = 0.005f;
                                    if (D > 0.f) {
                                        rtengine::guidedFilter(Y2, Yc, Yc, radius, epsilon, multiThread);
                                    }
                                }
                                float blend = 0.01 * params->locallab.spots.at(sp).ghs_LC;
                                blend =  rtengine::max(0.0001f, blend);
                                //end local contrast integrate to stretch
                       
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float r = tmpImage->r(i, j)/range;
                                    float g = tmpImage->g(i, j)/range;
                                    float b = tmpImage->b(i, j)/range;
                                    float Ro = 0.f;
                                    float Go = 0.f;
                                    float Bo = 0.f;
                                    if (met == 0) {
                                        float tlc = Yc[i][j];
                                        tlc = rtengine::max(tlc, noise);
                                        float ci = GHT(tlc, B, D, LP, SP, HP, c, strtype);
                                        float flc = ci / tlc;
                                        float gh = 0.f;
                                        if (shiftwhitepoint < low_limit_white_point) {//comparison between the calculate WP and low_limit_white_point (low white point)
                                            gh = norm(r, g, b, wprof);////Calculate Luminance in function working profile Wprof and norm
                                        } else if (shiftwhitepoint  < reasonable_limit_white_point) {//comparison between the calculated WP and the reasonable limit
                                            gh = norm2(r, g, b, wprof);//Calculate Luminance in function working profile Wprof and norm2
                                        } else {
                                            gh = norm_3(r, g, b, wprof, ghsbpwpvalue[1] / reasonable_limit_white_point);//Calculate Luminance in function working profile Wprof and norm_3
                                        }

                                        gh = rtengine::max(gh, noise);
                                        float Mgh = GHT(gh, B, D, LP, SP, HP, c, strtype);//ghs transform with "luminance"
                                        float fgh = Mgh / gh;
                                        fgh = intp(blend, flc, fgh);
          
                                        Ro = r * fgh;//new values for r, g, b
                                        Go = g * fgh;
                                        Bo = b * fgh;
                                        apply_sat(Ro, Go, Bo, fgh, gh);//always apply saturation

                                    } else if (met == 1) {
                                        float gh = norm(r, g, b, wprof);//Calculate Luminance in function working profile Wprof
                                        r = rtengine::max(r, noise);
                                        g = rtengine::max(g, noise);
                                        b = rtengine::max(b, noise);

                                        Ro = GHT(r, B, D, LP, SP, HP, c, strtype);//ghs R RGB standard
                                        Go = GHT(g, B, D, LP, SP, HP, c, strtype);//ghs G RGB standard
                                        Bo = GHT(b, B, D, LP, SP, HP, c, strtype);//ghs B RGB standard

                                        float sumRatio = 0.f;
                                        int count = 0;
                                        if (r != 0.f) {
                                            sumRatio += Ro / r;
                                            ++count;
                                        }
                                        if (g != 0.f) {
                                            sumRatio += Go / g;
                                            ++count;
                                        }
                                        if (b != 0.f) {
                                            sumRatio += Bo / b;
                                            ++count;
                                        }
                                        float fgh = count > 0 ? (sumRatio / static_cast<float>(count)) : 1.f;//linear average of the available channels

                                        apply_sat(Ro, Go, Bo, fgh, gh);//always apply saturation
                                    }
                                   // rebuild tmpImage with limit 0.00001f to avoid crash after SE 
                                    tmpImage->r(i, j) = rtengine::max(epsilg, Ro * range);//epsil 0.00001f to avoid crash
                                    tmpImage->g(i, j) = rtengine::max(epsilg, Go * range);
                                    tmpImage->b(i, j) = rtengine::max(epsilg, Bo * range);
                                }
                        } else if (met ==3 || met == 4 || met == 5) {//Luminance Saturation Hue HSL
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float r = tmpImage->r(i, j);
                                    float g = tmpImage->g(i, j);
                                    float b = tmpImage->b(i, j);
                                    float h, s, l;
                                    Color::rgb2hsl(r, g, b, h, s, l);
                                    if (met == 4) {//saturation
                                        s = GHT(s, B, D, LP, SP, HP, c, strtype);
                                    } else if (met == 3) {//luminance HSL
                                        l = rtengine::max(l, noise);
                                        l = GHT(l, B, D, LP, SP, HP, c, strtype);
                                    } else if (met == 5) {//hue
                                        h = GHT(h, B, D, LP, SP, HP, c, strtype);
                                    }
                                    float R, G, B;
                                    Color::hsl2rgb(h, s, l, R, G, B);
                                    tmpImage->r(i, j) = rtengine::max(epsilg, R);//epsilg 0.00001f to avoid crash
                                    tmpImage->g(i, j) = rtengine::max(epsilg, G);
                                    tmpImage->b(i, j) = rtengine::max(epsilg, B);
                                }
                        } else if (met == 2) {// Luminance chromaticity Lab mode
                            const std::unique_ptr<LabImage> labtemp(new LabImage(bfw, bfh));
                            rgb2lab(*tmpImage, *labtemp, params->icm.workingProfile);
                            const float satreal = ghschro;

                            DiagonalCurve color_satur({//curve for smoothing chroma ++
                                DCT_NURBS,
                                0, 0,
                                0.2, 0.2f + satreal / 250.f,
                                0.6, rtengine::min(1.f, 0.6f + satreal / 250.f),
                                1, 1
                            });

                            DiagonalCurve color_saturmoins({//curve for smoothing chroma --
                                DCT_NURBS,
                                0, 0,
                                0.1f - satreal / 150.f, 0.1f,
                                rtengine::min(1.f, 0.7f - satreal / 300.f), 0.7,
                                1, 1
                            });
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float lLab = labtemp->L[i][j]/32768.f;
                                    float alab = labtemp->a[i][j];
                                    float blab = labtemp->b[i][j];
                                    //Chromaticity
                                    float Chprov = std::sqrt(SQR(alab) + SQR(blab));
                                    float2 sincosval;
                                    sincosval.y = Chprov == 0.0f ? 1.f : alab / Chprov;
                                    sincosval.x = Chprov == 0.0f ? 0.f : blab / Chprov;
                                    if (ghschro > 0.f){
                                        Chprov = static_cast<float>(color_satur.getVal(LIM01(Chprov / 35000.f)));
                                    } else {
                                        Chprov = static_cast<float>(color_saturmoins.getVal(LIM01(Chprov / 35000.f)));
                                    }
                                    float chl = LIM01(Chprov);//in case of very very strong chromaticity to be sure no clip
                                    chl = GHT(chl, B, D, LP, SP, HP, c, strtype);//Chromaticity GHS
                                    Chprov = chl * 35000.f;
                                    alab = Chprov * sincosval.y;
                                    blab = Chprov * sincosval.x;
                                    //Luminance with new slope
                                    lLab = gammalog(lLab, gamma1, ts1, g_a[3], g_a[4]);//slope factor
                                    lLab = rtengine::max(lLab, noise);
                                    lLab = GHT(lLab, B, D, LP, SP, HP, c, strtype);//Luminance GHS                                   
                                    lLab = igammalog(lLab, gamma1, ts1, g_a[2], g_a[4]);//inverse slope factor

                                    labtemp->L[i][j] = lLab * 32768.f;
                                    labtemp->a[i][j] = alab;//restore Lab values
                                    labtemp->b[i][j] = blab;
                                }
                            lab2rgb(*labtemp, *tmpImage, params->icm.workingProfile);
                        }
          
                        if (params->locallab.spots.at(sp).ghsMatmet != "none") {
                            if (isrgb) {//mode RGB for cat16 and JZ, and also Agx
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif                                            
                                for (int i = 0; i < bfh; ++i)
                                    for (int j = 0; j < bfw; ++j) {
                                        const float r = tmpImage->r(i, j);
                                        const float g = tmpImage->g(i, j);
                                        const float b = tmpImage->b(i, j);
                                        std::array<float, 3> rgb_in{r, g, b};
                                        float rout = 0.f;
                                        float gout = 0.f;
                                        float bout = 0.f;
                                        Color::agx_trans(rgb_in, inv_lms_T, rout, gout, bout);
                                        tmpImage->r(i, j) = rtengine::max(epsilg, rout);//avoid negative values which are mathematically possible due to the values 
                                        tmpImage->g(i, j) = rtengine::max(epsilg, gout);//​​of the inverse matrix and the possible 'overflows' of the GHS calculations if the user uses very strong settings
                                        tmpImage->b(i, j) = rtengine::max(epsilg, bout);
                                    }
                            } else {//cat16 and JZ in XYZ mode
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif                                            

                                for (int i = 0; i < bfh; ++i)
                                    for (int j = 0; j < bfw; ++j) {
                                        const float r = tmpImage->r(i, j);
                                        const float g = tmpImage->g(i, j);
                                        const float b = tmpImage->b(i, j);
                                        float X, Y, Z;
                                        Color::rgbxyz(r, g, b, X, Y, Z, wpi);//convert to XYZ using the working profile
                                        std::array<float, 3> xyz_in{X, Y, Z};
                                        float Xout = 0.f;
                                        float Yout = 0.f;
                                        float Zout = 0.f;
                                        Color::agx_trans(xyz_in, inv_lms_T, Xout, Yout, Zout);//multiplies the XYZ data with the conversion inverse matrice Cat16 or JZ
                                        float rout = 0.f;
                                        float gout = 0.f;
                                        float bout = 0.f;
                                        Color::xyz2rgb(Xout, Yout, Zout, rout, gout, bout, wip);//convert to RGB using inverse working profile.
                                        tmpImage->r(i, j) = rtengine::max(epsilg, rout);//avoid negative values. Normally this should never happen because the coefficients of the selected matrix are all positive... unless the matrix changes
                                        tmpImage->g(i, j) = rtengine::max(epsilg, gout);//these potentially negative values, related to calculations and not to the gamut, are not accepted by the rgblab or labrgb, workingtrc functions, etc,
                                        tmpImage->b(i, j) = rtengine::max(epsilg, bout);//but after numerous checks, this has no impact on the results...except to prevent a crash.
                                    }
                            }
                        }
 
                        if (strtype == GHTStrType::INVERSE) {//inverse GHS
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif                       
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    tmpImage->r(i, j) = clipR(rtengine::max(epsilg, tmpImage->r(i, j)));//0.0001f to avoid crash
                                    tmpImage->g(i, j) = clipR(rtengine::max(epsilg, tmpImage->g(i, j)));//clipR to avoid crash in inverse GHS
                                    tmpImage->b(i, j) = clipR(rtengine::max(epsilg, tmpImage->b(i, j)));
                                }
                        } else if (strtype == GHTStrType::NORMAL) {//GHS
#ifdef _OPENMP
            #pragma omp parallel for if (multiThread)
#endif                       
                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    tmpImage->r(i, j) = rtengine::max(epsilg, tmpImage->r(i, j));//epsil 0.00001f to avoid crash after SE with RGB functions
                                    tmpImage->g(i, j) = rtengine::max(epsilg, tmpImage->g(i, j));
                                    tmpImage->b(i, j) = rtengine::max(epsilg, tmpImage->b(i, j));
                                }
                        }

                        if (smoth && D > 0.f) {
                            //Highlight attenuation in function of HP - protect highlight
                            tone_eqsmooth(this, tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread);//reduce Ev > 0 < 12
                        }

                        if (MID != 0.f  && D > 0.f) {
                            //midtones with tone_equ
                            ImProcFunctions::tone_eqcam(this, tmpImage.get(), MID, params->icm.workingProfile, sk, multiThread);
                        }

                        //Estimated current Middle grey at the end of GHS (midgrey)
                        //Absolute maximum RGB data at the end of GHS (maxdata)
                        //3 sigma is very representative of the data in use at the end of GHS (stdd)
                        float midgrey = 0.f;
                        float stdd = 0.f;
                        float maxdata = 0.f;
                        const int size = bfh * bfw;
                        constexpr float eps = 0.0001f;

#ifdef _OPENMP
        #   pragma omp parallel for reduction(+:midgrey, stdd) reduction(max:maxdata) if (multiThread)
#endif
                        for (int i = 0; i < bfh; ++i){
                            for (int j = 0; j < bfw; ++j) {
                                const float r = tmpImage->r(i, j);
                                const float g = tmpImage->g(i, j);
                                const float b = tmpImage->b(i, j);
                                float maxrgb = rtengine::max(r, g, b);
                                if (maxrgb > maxdata){
                                    maxdata = maxrgb;
                                }
                                if (shiftwhitepoint < low_limit_white_point) {
                                    midgrey += norm(r, g, b, wprof);//Mean luminance
                                    stdd += SQR(norm(r, g, b, wprof));//Standard deviation
                                } else if (shiftwhitepoint < reasonable_limit_white_point) {
                                    midgrey += norm2(r, g, b, wprof);
                                    stdd += SQR(norm2(r, g, b, wprof));
                                } else {
                                    midgrey +=  norm_3(r, g, b, wprof, shiftwhitepoint / reasonable_limit_white_point);
                                    stdd += SQR(norm_3(r, g, b, wprof, shiftwhitepoint / reasonable_limit_white_point));
                                }
                                tmpImage->r(i, j) = rtengine::max(eps, r);//avoid negatives values
                                tmpImage->g(i, j) = rtengine::max(eps, g);
                                tmpImage->b(i, j) = rtengine::max(eps, b);
                            }

                        }

                        midgrey /= size;
                        stdd /= size;
                        stdd -= SQR(midgrey);
                        float stdf = std::sqrt(stdd);
                        midgrey /= 65535.f;
                        stdf /= 65535.f;
                        ghsmid = midgrey;
                        ghs3sig = midgrey + (3.5f * stdf);//three sigma and half - if Gaussian distribution more than 99.7% data (of course it's not)
                        lp.maxdataghs = maxdata;//values above 65535 must, at this stage of the process, be exceptional.

                        if (lp.maxdataghs > 65535.f) {//Reduce the maximum value to acceptable limits 'limit_maxdata_auto = 1.4f' in tone_eqsmooth.
                                                     //after this limit user must enable the other GHS tools.
                                                     //this approach is clearly preferable to CLIP.
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i){
                                for (int j = 0; j < bfw; ++j) {
                                    const float r = tmpImage->r(i, j);
                                    const float g = tmpImage->g(i, j);
                                    const float b = tmpImage->b(i, j);
                                    tmpImage->r(i, j) = clipR(r);//clip data for tone_eqsmooth to avoid crash, but effective at reducing high values
                                    tmpImage->g(i, j) = clipR(g);
                                    tmpImage->b(i, j) = clipR(b);
                                }
                            }

                            tone_eqsmooth(this, tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread);//reduce Ev for maxdata

                            float maxdata2 = 0.f;//recalculates the maximum value of the data
#ifdef _OPENMP
        #   pragma omp parallel for reduction(max:maxdata2) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i){
                                for (int j = 0; j < bfw; ++j) {
                                    const float r = tmpImage->r(i, j);
                                    const float g = tmpImage->g(i, j);
                                    const float b = tmpImage->b(i, j);
                                    float maxrgb2 = rtengine::max(r, g, b);
                                    if (maxrgb2 > maxdata2){
                                        maxdata2 = maxrgb2;
                                    }
                                }
                            }
                            lp.maxdataghs = maxdata2;

                        }
                        ghsmaxrgb = lp.maxdataghs / 65535.f;

                        if (ghs3sig > lp.maxdataghs / 65535.f) {//if the distribution is not Gaussian, then we take for 3.5 sigmas the real maximum.
                            ghs3sig = lp.maxdataghs / 65535.f;
                        }

                        //conversion rgb to Lab in two phases rgb->XYZ then XYZ->Lab, it's the same thing but allows control of XYZ
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                            for (int i = 0; i < bfh; ++i){
                                for (int j = 0; j < bfw; ++j) {
                                    const float r = tmpImage->r(i, j);
                                    const float g = tmpImage->g(i, j);
                                    const float b = tmpImage->b(i, j);
                                    float x, y, z;
                                    Color::rgbxyz (r, g, b, x, y, z, wpi);
                                    z = max(z, eps);//prevents negative values ​​of XYZ
                                    y = max(y, eps);
                                    x = max(x, eps);
                                    float Lexp, aexp, bexp;
                                    Color::XYZ2Lab(x, y, z, Lexp, aexp, bexp);
                                    bufexpfin->L[i][j] = Lexp;
                                    bufexpfin->a[i][j] = aexp;
                                    bufexpfin->b[i][j] = bexp;
                                }
                            }

                        tmpImage.reset();
                        //local contrast minimum
                        double kmod = 2.2;
                        if (met == 0) {
                            kmod = 1.6;
                        }
                        float rad = kmod * params->locallab.spots.at(sp).ghs_LC;
                        float stren = 15.f * (1.f + D);//take into account D stretch
                        if (D > 0.f) {
                            loccont(bfw, bfh, bufexpfin.get(), rad, stren, sk); //local contrast in L (Lab) mode.
                        }
                    }
                }
                if (lp.shmeth == 3) {//Michaelis-Menten - 2026
                    const float michexp = params->locallab.spots.at(sp).mich_exp;//Exposure
                    const float michspar = params->locallab.spots.at(sp).mich_spar;//Output scale
                    const float michkpar = params->locallab.spots.at(sp).mich_kpar;//Knee strength
                    const float michsat = params->locallab.spots.at(sp).mich_sat;//Saturation
                    const float michout = params->locallab.spots.at(sp).mich_out;//Output max clamp
                    const bool michblack = params->locallab.spots.at(sp).mich_black;//Linear Black point
                    const bool michwhite = params->locallab.spots.at(sp).mich_white;//Linear White point
                    const float michhigh = params->locallab.spots.at(sp).mich_high;//Highlight reduction
                    const bool midjdx = params->locallab.spots.at(sp).mich_jdx;//Matrix LMS using XYZ transform
                    constexpr float epsilm = 0.00001f;

                    constexpr float range = 65535.f;
                    std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));
                    lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile);//Conversion Lab -> RGB
                    float minbmich = 100.f;
                    float maxwmich = -100.f;
                    bool calculatbw = false;
                    calculatbw = michblack || michwhite;

                    //preparing Matrix conversion
                    using Triple = std::array<double, 3>;
                    using Matrix = std::array<Triple, 3>;
                    Matrix inv_lms_T = {};//initialize inv_lms_T
                    TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile);
                    TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
                    //inverse matrix user select
                    const float wip[3][3] = {
                        {static_cast<float>(wiprof[0][0]), static_cast<float>(wiprof[0][1]), static_cast<float>(wiprof[0][2])},
                        {static_cast<float>(wiprof[1][0]), static_cast<float>(wiprof[1][1]), static_cast<float>(wiprof[1][2])},
                        {static_cast<float>(wiprof[2][0]), static_cast<float>(wiprof[2][1]), static_cast<float>(wiprof[2][2])}
                    };
                    TMatrix wprofi = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
                    const float wpi[3][3] = {
                        {static_cast<float>(wprofi[0][0]), static_cast<float>(wprofi[0][1]), static_cast<float>(wprofi[0][2])},
                        {static_cast<float>(wprofi[1][0]), static_cast<float>(wprofi[1][1]), static_cast<float>(wprofi[1][2])},
                        {static_cast<float>(wprofi[2][0]), static_cast<float>(wprofi[2][1]), static_cast<float>(wprofi[2][2])}
                        };
                    const Matrix lms_mat = {{//JDx - Jacques Desmis Matrix XYZ -> LMS - Simple matrix that amplifies the current channel.
                        { 0.83, 0.1, 0.07 },//Red (L) predominant in LMS with a little more green
                        { 0.12, 0.78, 0.1 },//Green (M) almost neutral, with a little more red
                        { 0.11, 0.09, 0.80 }//Blue (S) almost neutral, with a little more red
                    }};

                    Matrix lms_T = {};
                    Color::transpose(lms_mat, lms_T);//transpose Matrix
                    //invert matrix
                    if (midjdx) {
                        if (!rtengine::invertMatrix(lms_T, inv_lms_T)) {
                            if (settings->verbose) {
                                std::cout << "Matrix is not invertible, skipping and use this one" << std::endl;
                            }
                            //If the calculations fail, we use this matrix calculated with a spreadsheet. Note that if 'lms_mat' changes, you must redo the calculations.
                            inv_lms_T[0][0] = 1.238171935;
                            inv_lms_T[0][1] = -0.148379303;
                            inv_lms_T[0][2] = -0.089792631;
                            inv_lms_T[1][0] = -0.171129454;
                            inv_lms_T[1][1] = 1.321320717;
                            inv_lms_T[1][2] = -0.150191262;
                            inv_lms_T[2][0] = -0.150996577;
                            inv_lms_T[2][1] = -0.128246426;
                            inv_lms_T[2][2] = 1.279243004;
                        }

#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int i = 0; i < bfh; ++i)
                            for (int j = 0; j < bfw; ++j) {
                                const float r = tmpImage->r(i, j);
                                const float g = tmpImage->g(i, j);
                                const float b = tmpImage->b(i, j);
                                float X, Y, Z;
                                Color::rgbxyz(r, g, b, X, Y, Z, wpi);//convert to XYZ using the working profile
                                std::array<float, 3> xyz_in{X, Y, Z};
                                float Xout = 0.f;
                                float Yout = 0.f;
                                float Zout = 0.f;
                                Color::agx_trans(xyz_in, lms_T, Xout, Yout, Zout);//multiplies the XYZ data with the conversion matrice Cat16 or JZ
                                float rout = 0.f;
                                float gout = 0.f;
                                float bout = 0.f;
                                Color::xyz2rgb(Xout, Yout, Zout, rout, gout, bout, wip);//convert to RGB using inverse working profile.
                                tmpImage->r(i, j) = rtengine::max(epsilm, rout);//avoid negative values. Normally this should never happen because the coefficients of the selected matrix are all positive... unless the matrix changes
                                tmpImage->g(i, j) = rtengine::max(epsilm, gout);//these potentially negative values, related to calculations and not to the gamut, are not accepted by the rgblab or labrgb, workingtrc functions, etc,
                                tmpImage->b(i, j) = rtengine::max(epsilm, bout);//but after numerous checks, this has no impact on the results...except to prevent a crash.
                            }
                    }

                    if (calculatbw) {//Calculate linear minimum black and maximum white
 #ifdef _OPENMP
        #   pragma omp parallel for reduction(min:minbmich) reduction(max:maxwmich) if (multiThread)
#endif
                        for (int i = 0; i < bfh; ++i)
                            for (int j = 0; j < bfw; ++j) {
                                float r = tmpImage->r(i, j) / range;
                                float g = tmpImage->g(i, j) / range;
                                float b = tmpImage->b(i, j) / range;
                                float minrgb = rtengine::min(r, g, b);
                                if (minrgb < minbmich){
                                    minbmich = minrgb;
                                }
                                float maxrgb = rtengine::max(r, g, b);
                                if (maxrgb > maxwmich){
                                    maxwmich = maxrgb;
                                }
                            }
                    } else { //default values
                        minbmich = 0.f;
                        maxwmich = 1.f;
                    }
                    const float noise = pow_F(2.f, -16.f);//very low value

                    minbmich = rtengine::max(minbmich, noise);//set a very minimal value in all cases to avoid 0
                    const auto sf =
                        [=](float s, float c) -> float
                        {
                            if (c > noise) {
                                return 1.f - min(std::abs(s) / c, 1.f);
                            } else {
                                return 0.f;
                            }
                        };
                    //saturation
                    const auto apply_sat =
                        [&](float &r, float &g, float &b, float f, float ll) -> void
                        {
                            float rl = r - ll;
                            float gl = g - ll;
                            float bl = b - ll;
                            // The parameters 0.2f, 0.6f, and 0.4f control the nonlinearity and scaling of the saturation adjustment:
                            // - 0.2f: exponent for the power function, affecting the response curve of the adjustment factor.
                            // - 0.6f: scaling factor for the powered value.
                            // - 0.4f: base offset added to ensure a minimum effect.
                            // Adjust these values to fine-tune the strength and shape of the local saturation effect.
                            float s = intp(max(sf(rl, r), sf(gl, g), sf(bl, b)), pow_F(f, 0.35f) * 0.6f + 0.4f, 1.f);//0.35f - MM is desaturating a lot.
                            r = ll + s * rl;
                            g = ll + s * gl;
                            b = ll + s * bl;
                        };

                    float deltawp = rtengine::max(0.05f, maxwmich - minbmich);//Linear Dynamic Range - 0.05 minimum acceptable
                    if (!michwhite) {//no use of linear Dynamic Range
                        deltawp = 1.f;
                    }
                    //I put these 2 variables in place, just in case... So as not to rewrite the code... If users request a finer setting than "subtraction" or "Dynamic range", and an action on the White point.
                    michbwslider[0]= minbmich;
                    michbwslider[1]= maxwmich;
                    if (settings->verbose) {
                        printf("Min black=%f max White=%f\n", (double) michbwslider[0], (double) michbwslider[1]);
                    }

                    const float gain = pow_F(2.f, michexp);//in Ev

#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                    //Taken from ART's CTLs, thanks to Alberto Griggio
                    //Modified by Jacques Desmis
                    for (int i = 0; i < bfh; ++i)
                        for (int j = 0; j < bfw; ++j) {
                            float r = ((tmpImage->r(i, j) / range) - minbmich) / deltawp;// Subtract linear black and use Linear Dynamic Range
                            float g = ((tmpImage->g(i, j) / range) - minbmich) / deltawp;//data are in range [0 1]
                            float b = ((tmpImage->b(i, j) / range) - minbmich) / deltawp;

                            // --- Apply exposure ---
                            float r_exposed = r * gain;
                            float g_exposed = g * gain;
                            float b_exposed = b * gain;
                            float mmh = norm(r, g, b, wprof);

                            // --- Ensure non-negative inputs for the curve ---
                            float r_linear = fmax(0.f, r_exposed);
                            float g_linear = fmax(0.f, g_exposed);
                            float b_linear = fmax(0.f, b_exposed);

                            // --- Apply Michaelis-Menten curve per channel ---
                            float r_tonemapped = mm_curve(r_linear, michspar, michkpar);
                            float g_tonemapped = mm_curve(g_linear, michspar, michkpar);
                            float b_tonemapped = mm_curve(b_linear, michspar, michkpar);
                            //Excellent suggestion from Copilot
                            float sumRatio = 0.f;
                            int count = 0;
                            if (r != 0.f) {
                                sumRatio += r_tonemapped / r;
                                ++count;
                            }
                            if (g != 0.f) {
                                sumRatio += g_tonemapped / g;
                                ++count;
                            }
                            if (b != 0.f) {
                                sumRatio += b_tonemapped / b;
                                ++count;
                            }
                            float fmmm = count > 0 ? (sumRatio / static_cast<float>(count)) : 1.f;//linear average of the available channel
                            apply_sat(r_tonemapped, g_tonemapped, b_tonemapped, fmmm, mmh );//always apply saturation

                            // --- Saturation adjustment ---
                            float h, s, l;
                            Color::rgb2hsl(r_tonemapped * range, g_tonemapped * range, b_tonemapped * range, h, s, l);
                            s *= michsat;
                            s = fmax(0.f, s);
                            s = clamp(s, 0.f, 1.f);
                            float R, G, B;
                            Color::hsl2rgb(h, s, l, R, G, B);
                            float r_final = R / range;
                            float g_final = G / range;
                            float b_final = B / range;

                            // --- Final clamping and output ---
                            float rout = clamp(r_final, 0.f, michout);
                            float gout = clamp(g_final, 0.f, michout);
                            float bout = clamp(b_final, 0.f, michout);
                            tmpImage->r(i, j) = rtengine::max(epsilm, rout * range);//epsilm 0.00001f to avoid crash
                            tmpImage->g(i, j) = rtengine::max(epsilm, gout * range);
                            tmpImage->b(i, j) = rtengine::max(epsilm, bout * range);

                        }
                    
                    if (michhigh > 0.f ) {
                        //Highlight attenuation
                        tone_eqsmooth(this, tmpImage.get(), lp, params->icm.workingProfile, sk, multiThread);//reduce Ev > 0 < 12
                    }
 
                    if (midjdx) {//Second XYZ transformation from LMS (and RGB)
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif                                            

                        for (int i = 0; i < bfh; ++i)
                            for (int j = 0; j < bfw; ++j) {
                                const float r = tmpImage->r(i, j);
                                const float g = tmpImage->g(i, j);
                                const float b = tmpImage->b(i, j);
                                float X, Y, Z;
                                Color::rgbxyz(r, g, b, X, Y, Z, wpi);//convert to XYZ using the working profile
                                std::array<float, 3> xyz_in{X, Y, Z};
                                float Xout = 0.f;
                                float Yout = 0.f;
                                float Zout = 0.f;
                                Color::agx_trans(xyz_in, inv_lms_T, Xout, Yout, Zout);//multiplies the XYZ data with the conversion inverse matrice Cat16 or JZ
                                float rout = 0.f;
                                float gout = 0.f;
                                float bout = 0.f;
                                Color::xyz2rgb(Xout, Yout, Zout, rout, gout, bout, wip);//convert to RGB using inverse working profile.
                                tmpImage->r(i, j) = rtengine::max(epsilm, rout);//avoid negative values. Normally this should never happen because the coefficients of the selected matrix are all positive... unless the matrix changes
                                tmpImage->g(i, j) = rtengine::max(epsilm, gout);//these potentially negative values, related to calculations and not to the gamut, are not accepted by the rgblab or labrgb, workingtrc functions, etc,
                                tmpImage->b(i, j) = rtengine::max(epsilm, bout);//but after numerous checks, this has no impact on the results...except to prevent a crash.
                            }
                    }

                    rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile);//conversion RGB -> Lab
                }
                
                
                //gradient
                int GH = transformed->H;
                int GW = transformed->W;

                struct grad_params gp;
                //here with grad=0 - standard.
                if (lp.strSH != 0.f && (call == ca1 || call == ca2 || call == ca3)  && execgradsh) {//test to plain image
                    calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 2, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufexpfin->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                        }
                    }
                } else if (lp.strSH != 0.f && execgradsh && (call ==  ca4) && ((GW >= mDEN && GH >= mDEN))){//test to run in plain image - not used
                    calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, GW, GH, oW, oH, tX, tY, tW, tH, 2, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                    for (int ir = 0; ir < GH; ir++)
                        for (int jr = 0; jr < GW; jr++) {
                            tmp1->L[ir][jr] = original->L[ir][jr];
                            tmp1->a[ir][jr] = original->a[ir][jr];
                            tmp1->b[ir][jr] = original->b[ir][jr];                    
                    }
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                    for (int ir = 0; ir < GH; ir++) {
                        for (int jr = 0; jr < GW; jr++) {
                            tmp1->L[ir][jr] = ImProcFunctions::calcGradientFactor(gp, cy + jr, cx + ir);
                        }
                    }
                }
            //end gradient
            }

            if (lp.enaSHMask && lp.recothrs != 1.f) {
                float recoth = lp.recothrs;

                if (lp.recothrs < 1.f) {
                    recoth = -1.f * recoth + 2.f;
                }

                float hig = lp.higthrs;
                float low = lp.lowthrs;
                // float recoth = lp.recothrs;
                float decay = lp.decays;
                bool invmask = false;
                maskrecov(bufexpfin.get(), original, bufmaskorigSH.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
            }

            const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparsh;
            int bw = bufexporig->W;
            int bh = bufexporig->H;

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

            for (int x = 0; x < bh; x++) {
                for (int y = 0; y < bw; y++) {
                    bufexpfin->L[x][y] = intp(repart, bufexporig->L[x][y], bufexpfin->L[x][y]);
                    bufexpfin->a[x][y] = intp(repart, bufexporig->a[x][y], bufexpfin->a[x][y]);
                    bufexpfin->b[x][y] = intp(repart, bufexporig->b[x][y], bufexpfin->b[x][y]);
                }
            }

            if (lp.recothrs >= 1.f) {
                if(call == ca1 || call == ca2 || call == ca3) {//call == 2 to run in mode plain image
                    transit_shapedetect2(sp, 0.f, 0.f, call, 9, bufexporig.get(), bufexpfin.get(), originalmaskSH.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                } else if (call == ca4  && execgradsh) {// mode plain image call = 1
                    transit_shapedetect2(sp, 0.f, 0.f, call, 9, bufexporig.get(), bufexpfin.get(), originalmaskSH.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, tmp1.get(), grad, cx, cy, sk);                   
                } 
            } else {
                if(call == ca1 || call == ca2 || call == ca3) {//call == 2 to run in mode plain image
                    transit_shapedetect2(sp, 0.f, 0.f, call, 9, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD,  cx, cy, sk);
                } else if (call == ca4  && execgradsh) {// mode plain image              
                    transit_shapedetect2(sp, 0.f, 0.f, call, 9, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, tmp1.get(), grad, cx, cy, sk);
                }
            }

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    } else  if (lp.invsh && (lp.highlihs > 0.f || lp.shadowhs > 0.f || tonequ  || tonecurv || lp.showmaskSHmetinv == 1 || lp.enaSHMaskinv) && call < 3 && lp.hsena) {
        std::unique_ptr<LabImage> bufmaskblurcol;
        std::unique_ptr<LabImage> originalmaskSH;
        const std::unique_ptr<LabImage> bufcolorig(new LabImage(TW, TH));

        if (lp.enaSHMaskinv || lp.showmaskSHmetinv == 1) {
            bufmaskblurcol.reset(new LabImage(TW, TH, true));
            originalmaskSH.reset(new LabImage(TW, TH));
        }

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < TH ; y++) {
            for (int x = 0; x < TW; x++) {
                bufcolorig->L[y][x] = original->L[y][x];
            }
        }

        int inv = 1;
        bool showmaske = false;
        bool enaMask = false;
        bool deltaE = false;
        bool modmask = false;
        bool zero = false;
        bool modif = false;

        if (lp.showmaskSHmetinv == 1) {
            showmaske = true;
        }

        if (lp.enaSHMaskinv) {
            enaMask = true;
        }

        if (lp.showmaskSHmetinv == 0) {
            zero = true;
        }

        float chrom = lp.chromaSH;
        float rad = lp.radmaSH;
        float gamma = lp.gammaSH;
        float slope = lp.slomaSH;
        float blendm = lp.blendmaSH;
        float lap = params->locallab.spots.at(sp).lapmaskSH;
        bool pde = params->locallab.spots.at(sp).laplac;
        LocwavCurve dummy;
        int sco = params->locallab.spots.at(sp).scopemask;
        int shortcu = params->locallab.spots.at(sp).shortc;

        const float mindE = 2.f + MINSCOPE * sco * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
        int shado = 0;
        float amountcd = params->locallab.spots.at(sp).fatamountSH;
        float anchorcd = params->locallab.spots.at(sp).fatanchorSH;
        int lumask = params->locallab.spots.at(sp).lumask;
        LocHHmaskCurve lochhhmasCurve;
        const int highl = 0;
        maskcalccol(call, false, pde, TW, TH, oW, oH, tX, tY, tW, tH, 0, 0, 0, 0, sk, cx, cy, bufcolorig.get(), bufmaskblurcol.get(), originalmaskSH.get(), original, reserved, inv, lp,
                    0.f, false,
                    locccmasSHCurve, lcmasSHutili, locllmasSHCurve, llmasSHutili, lochhmasSHCurve, lhmasSHutili, lochhhmasCurve, false, multiThread,
                    enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskSHlocalcurve, localmaskSHutili, dummy, false, 1, 1, 5, 5,
                    shortcu, false, hueref, chromaref, lumaref,
                    maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                   );


        if (lp.showmaskSHmetinv == 1) {
            showmask(lumask, lp, 0, 0, cx, cy, TW, TH, bufcolorig.get(), transformed, bufmaskblurcol.get(), inv);

            return;
        }

        float adjustr = 2.f;
        InverseColorLight_Local(tonequ, tonecurv, sp, 2, lp, originalmaskSH.get(), lightCurveloc, hltonecurveloc, shtonecurveloc, tonecurveloc, exlocalcurve, cclocalcurve, adjustr, localcutili, lllocalcurve, locallutili, original, transformed, cx, cy, hueref, chromaref, lumaref, sk);

        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }
    }

// soft light and retinex_pde
    if ((lp.strng > 1.f || lp.prevdE) && call <= 3 && lp.sfena) {
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;
        //variable for fast FFTW
        int bfhr = bfh;
        int bfwr = bfw;

        if (bfw >= mSP && bfh >= mSP) {

            if (lp.softmet == 1) {
                optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh));

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend; y++) {
                for (int x = xstart; x < xend; x++) {
                    bufexporig->L[y - ystart][x - xstart] = original->L[y][x];
                    bufexporig->a[y - ystart][x - xstart] = original->a[y][x];
                    bufexporig->b[y - ystart][x - xstart] = original->b[y][x];
                }
            }

            bufexpfin->CopyFrom(bufexporig.get(), multiThread);
            SoftLightParams softLightParams;
            softLightParams.enabled = true;
            softLightParams.strength = lp.strng;

            if (lp.softmet == 0) {
                ImProcFunctions::softLight(bufexpfin.get(), softLightParams);
            } else if (lp.softmet == 1) {
                const std::unique_ptr<float[]> datain(new float[bfwr * bfhr]);
                const std::unique_ptr<float[]> dataout(new float[bfwr * bfhr]);
                const std::unique_ptr<float[]> dE(new float[bfwr * bfhr]);

                deltaEforLaplace(dE.get(), lp.lap, bfwr, bfhr, bufexpfin.get(), hueref, chromaref, lumaref);

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfhr; y++) {
                    for (int x = 0; x < bfwr; x++) {
                        datain[y * bfwr + x] = bufexpfin->L[y][x];
                    }
                }

                const int showorig = lp.showmasksoftmet >= 5 ? 0 : lp.showmasksoftmet;
                MyMutex::MyLock lock(*fftwMutex);
                ImProcFunctions::retinex_pde(datain.get(), dataout.get(), bfwr, bfhr, 8.f * lp.strng, 1.f, dE.get(), showorig, 1, 1);
#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfhr; y++) {
                    for (int x = 0; x < bfwr; x++) {
                        bufexpfin->L[y][x] = dataout[y * bfwr + x];
                    }
                }
            }

            transit_shapedetect2(sp, 0.f, 0.f, call, 3, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }

    //local contrast
    bool wavcurve = false;
    bool wavcurvelev = false;
    bool wavcurvecon = false;
    bool wavcurvecomp = false;
    bool wavcurvecompre = false;

    if (lp.locmet == 1) {
        if (locwavCurve && locwavutili) {
            for (int i = 0; i < 500; i++) {
                if (locwavCurve[i] != 0.5f) {
                    wavcurve = true;
                    break;
                }
            }
        }

        if (loclevwavCurve && loclevwavutili) {
            for (int i = 0; i < 500; i++) {
                if (loclevwavCurve[i] != 0.f) {
                    wavcurvelev = true;
                    break;
                }
            }
        }

        if (locconwavCurve && locconwavutili) {
            for (int i = 0; i < 500; i++) {
                if (locconwavCurve[i] != 0.5f) {
                    wavcurvecon = true;
                    break;
                }
            }
        }

        if (loccompwavCurve && loccompwavutili) {
            for (int i = 0; i < 500; i++) {
                if (loccompwavCurve[i] != 0.f) {
                    wavcurvecomp = true;
                    break;
                }
            }
        }

        if (loccomprewavCurve && loccomprewavutili) {
            for (int i = 0; i < 500; i++) {
                if (loccomprewavCurve[i] != 0.75f) {
                    wavcurvecompre = true;
                    break;
                }
            }
        }
    }

    if ((lp.lcamount > 0.f || wavcurve || lp.showmasklcmet == 2 || lp.enalcMask || lp.showmasklcmet == 3 || lp.showmasklcmet == 4 || lp.prevdE || lp.strwav != 0.f || wavcurvelev || wavcurvecon || wavcurvecomp || wavcurvecompre || lp.edgwena || params->locallab.spots.at(sp).residblur > 0.0 || params->locallab.spots.at(sp).levelblur > 0.0 || params->locallab.spots.at(sp).residcont != 0.0 || params->locallab.spots.at(sp).clarilres != 0.0 || params->locallab.spots.at(sp).claricres != 0.0) && call <= 3 && lp.lcena) {

        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;
        int bfhr = bfh;
        int bfwr = bfw;

        if (bfw >= mSPwav && bfh >= mSPwav) {//avoid too small spot for wavelet
            if (lp.ftwlc) {
                optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            std::unique_ptr<LabImage> bufmaskblurlc;
            std::unique_ptr<LabImage> originalmasklc;
            std::unique_ptr<LabImage> bufmaskoriglc;

            if (lp.showmasklcmet == 2  || lp.enalcMask || lp.showmasklcmet == 3 || lp.showmasklcmet == 4) {
                bufmaskblurlc.reset(new LabImage(bfw, bfh));
                originalmasklc.reset(new LabImage(bfw, bfh));
                bufmaskoriglc.reset(new LabImage(bfw, bfh));
            }

            array2D<float> buflight(bfw, bfh);
            JaggedArray<float> bufchro(bfw, bfh);
            const std::unique_ptr<LabImage> bufgb(new LabImage(bfw, bfh));
            std::unique_ptr<LabImage> tmp1(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> tmpresid(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> tmpres(new LabImage(bfw, bfh));

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend; y++) {
                for (int x = xstart; x < xend; x++) {
                    bufgb->L[y - ystart][x - xstart] = original->L[y][x];
                    bufgb->a[y - ystart][x - xstart] = original->a[y][x];
                    bufgb->b[y - ystart][x - xstart] = original->b[y][x];
                    tmp1->L[y - ystart][x - xstart] = original->L[y][x];
                    tmp1->a[y - ystart][x - xstart] = original->a[y][x];
                    tmp1->b[y - ystart][x - xstart] = original->b[y][x];
                    tmpresid->L[y - ystart][x - xstart] = original->L[y][x];
                    tmpresid->a[y - ystart][x - xstart] = original->a[y][x];
                    tmpresid->b[y - ystart][x - xstart] = original->b[y][x];
                    tmpres->L[y - ystart][x - xstart] = original->L[y][x];
                    tmpres->a[y - ystart][x - xstart] = original->a[y][x];
                    tmpres->b[y - ystart][x - xstart] = original->b[y][x];
                }
            }

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bfh; y++) {
                for (int x = 0; x < bfw; x++) {
                    bufgb->L[y][x] = original->L[y + ystart][x + xstart];
                }
            }

            int inv = 0;
            bool showmaske = false;
            bool enaMask = false;
            bool deltaE = false;
            bool modmask = false;
            bool zero = false;
            bool modif = false;

            if (lp.showmasklcmet == 3) {
                showmaske = true;
            }

            if (lp.enalcMask) {
                enaMask = true;
            }

            if (lp.showmasklcmet == 4) {
                deltaE = true;
            }

            if (lp.showmasklcmet == 2) {
                modmask = true;
            }

            if (lp.showmasklcmet == 1) {
                modif = true;
            }

            if (lp.showmasklcmet == 0) {
                zero = true;
            }


            float chrom = lp.chromalc;
            float rad = lp.radmalc;
            float blendm = lp.blendmalc;
            float gamma = 1.f;
            float slope = 0.f;
            float lap = 0.f; //params->locallab.spots.at(sp).lapmaskexp;
            bool pde = false; //params->locallab.spots.at(sp).laplac;
            LocwavCurve dummy;
            int sco = params->locallab.spots.at(sp).scopemask;
            int shado = 0;
            int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;
            const float mindE = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            float amountcd = 0.f;
            float anchorcd = 50.f;
            int lumask = params->locallab.spots.at(sp).lumask;
            LocHHmaskCurve lochhhmasCurve;
            const int highl = 0;
            maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufgb.get(), bufmaskoriglc.get(), originalmasklc.get(), original, reserved, inv, lp,
                        0.f, false,
                        locccmaslcCurve, lcmaslcutili, locllmaslcCurve, llmaslcutili, lochhmaslcCurve, lhmaslcutili, lochhhmasCurve, false, multiThread,
                        enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmasklclocalcurve, localmasklcutili, dummy, false, 1, 1, 5, 5,
                        shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                        maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                       );

            if (lp.showmasklcmet == 3) {
                showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufgb.get(), transformed, bufmaskoriglc.get(), 0);

                return;
            }

            if (lp.showmasklcmet == 0 || lp.showmasklcmet == 1  || lp.showmasklcmet == 2 || lp.showmasklcmet == 4 || lp.enalcMask) {

                if (lp.locmet == 0) {
                    LocalContrastParams localContrastParams;
                    LocallabParams locallabparams;
                    localContrastParams.enabled = true;
                    localContrastParams.radius = params->locallab.spots.at(sp).lcradius;
                    localContrastParams.amount = params->locallab.spots.at(sp).lcamount;
                    localContrastParams.darkness = params->locallab.spots.at(sp).lcdarkness;
                    localContrastParams.lightness = params->locallab.spots.at(sp).lightness;
                    bool fftwlc = false;

                    if (!lp.ftwlc) { // || (lp.ftwlc && call != 2)) {
                        ImProcFunctions::localContrast(tmp1.get(), tmp1->L, localContrastParams, fftwlc, sk);
                    } else {
                        const std::unique_ptr<LabImage> tmpfftw(new LabImage(bfwr, bfhr));
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfhr; y++) {
                            for (int x = 0; x < bfwr; x++) {
                                tmpfftw->L[y][x] = tmp1->L[y][x];
                                tmpfftw->a[y][x] = tmp1->a[y][x];
                                tmpfftw->b[y][x] = tmp1->b[y][x];
                            }
                        }

                        fftwlc = true;
                        ImProcFunctions::localContrast(tmpfftw.get(), tmpfftw->L, localContrastParams, fftwlc, sk);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfhr; y++) {
                            for (int x = 0; x < bfwr; x++) {
                                tmp1->L[y][x] = tmpfftw->L[y][x];
                                tmp1->a[y][x] = tmpfftw->a[y][x];
                                tmp1->b[y][x] = tmpfftw->b[y][x];
                            }
                        }

                    }
                } else if (lp.locmet == 1) { //wavelet && sk ==1
                    int wavelet_level = 1 + params->locallab.spots.at(sp).csthreshold.getBottomRight();//retrieve with +1 maximum wavelet_level
                    wavelet_level = rtengine::max(5, wavelet_level);
                    float mL = params->locallab.spots.at(sp).clarilres / 100.0;
                    float mC = params->locallab.spots.at(sp).claricres / 100.0;
                    float softr = params->locallab.spots.at(sp).clarisoft;
                    float mL0 = 0.f;
                    float mC0 = 0.f;
#ifdef _OPENMP
                    const int numThreads = omp_get_max_threads();
#else
                    const int numThreads = 1;

#endif
                    // adap maximum level wavelet to size of RT-spot
                    int minwin = rtengine::min(bfw, bfh);
                    int maxlevelspot = 9;//maximum possible

                    // adap maximum level wavelet to size of crop
                    while ((1 << maxlevelspot) >= (minwin) && maxlevelspot  > 1) {
                        --maxlevelspot ;
                    }


                    wavelet_level = rtengine::min(wavelet_level, maxlevelspot);
                    bool exec = false;
                    bool origlc = params->locallab.spots.at(sp).origlc;

                    if (origlc) {//merge only with original
                        clarimerge(lp, mL, mC, exec, tmpresid.get(), wavelet_level, sk, numThreads);
                    }

                    int maxlvl = wavelet_level;
                    const float contrast = params->locallab.spots.at(sp).residcont;
                    int level_bl = params->locallab.spots.at(sp).csthreshold.getBottomLeft();
                    int level_hl = params->locallab.spots.at(sp).csthreshold.getTopLeft();
                    int level_br = params->locallab.spots.at(sp).csthreshold.getBottomRight();
                    int level_hr = params->locallab.spots.at(sp).csthreshold.getTopRight();
                    const float radblur = (params->locallab.spots.at(sp).residblur) / sk;
                    const bool blurlc = params->locallab.spots.at(sp).blurlc;
                    const float radlevblur = (params->locallab.spots.at(sp).levelblur) / sk;
                    const float sigma = params->locallab.spots.at(sp).sigma;
                    const float offs = params->locallab.spots.at(sp).offset;
                    const float sigmadc = params->locallab.spots.at(sp).sigmadc;
                    const float deltad = params->locallab.spots.at(sp).deltad;
                    // const float fatres = params->locallab.spots.at(sp).fatres;
                    const float chrol = params->locallab.spots.at(sp).chromalev;
                    const float chrobl = params->locallab.spots.at(sp).chromablu;
                    const bool blurena = params->locallab.spots.at(sp).wavblur;
                    const bool levelena = params->locallab.spots.at(sp).wavcont;
                    const bool comprena = params->locallab.spots.at(sp).wavcomp;
                    const bool compreena = params->locallab.spots.at(sp).wavcompre;
                    const float compress = params->locallab.spots.at(sp).residcomp;
                    const float thres = params->locallab.spots.at(sp).threswav;

                    float gamma = lp.gamlc;
                    rtengine::GammaValues g_a; //gamma parameters
                    double pwr = 1.0 / (double) lp.gamlc;//default 3.0 - gamma Lab
                    double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                    rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope

                    if (gamma != 1.f) {
#ifdef _OPENMP
                        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < tmp1->H; ++y) {
                            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < tmp1->W - 3; x += 4) {
                                STVFU(tmp1->L[y][x], F2V(32768.f) * igammalog(LVFU(tmp1->L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[2]), F2V(g_a[4])));
                            }

#endif

                            for (; x < tmp1->W; ++x) {
                                tmp1->L[y][x] = 32768.f * igammalog(tmp1->L[y][x] / 32768.f, gamma, ts, g_a[2], g_a[4]);
                            }
                        }
                    }

                    wavcontrast4(call, lp, tmp1->L, tmp1->a, tmp1->b, contrast, radblur, radlevblur, tmp1->W, tmp1->H, oW, oH, tX, tY, tW, tH, level_bl, level_hl, level_br, level_hr, sk, numThreads, locwavCurve, locwavutili, wavcurve, loclevwavCurve, loclevwavutili, wavcurvelev, locconwavCurve, locconwavutili, wavcurvecon, loccompwavCurve, loccompwavutili, wavcurvecomp, loccomprewavCurve, loccomprewavutili, wavcurvecompre, locedgwavCurve, locedgwavutili, sigma, offs, maxlvl, sigmadc, deltad, chrol, chrobl, blurlc, blurena, levelena, comprena, compreena, compress, thres, fw, fh, cx, cy, ksk, kskx, kbh, kbw);

                    if (params->locallab.spots.at(sp).expcie && params->locallab.spots.at(sp).modecie == "wav") {
                        bool HHcurvejz = false, CHcurvejz = false, LHcurvejz = false;

                        if (params->locallab.spots.at(sp).modecam == "jz") {//some cam16 elementsfor Jz
                            ImProcFunctions::ciecamloc_02float(lp, sp, tmp1.get(), bfw, bfh, 10, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                        }

                        ImProcFunctions::ciecamloc_02float(lp, sp, tmp1.get(), bfw, bfh, 0, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);

                        float rad = params->locallab.spots.at(sp).detailcie;
                        loccont(bfw, bfh, tmp1.get(), rad, 5.f, sk);
                    }



                    if (gamma != 1.f) {
#ifdef _OPENMP
                        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < tmp1->H; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < tmp1->W - 3; x += 4) {
                                STVFU(tmp1->L[y][x], F2V(32768.f) * gammalog(LVFU(tmp1->L[y][x]) / F2V(32768.f), F2V(gamma), F2V(ts), F2V(g_a[3]), F2V(g_a[4])));
                            }

#endif

                            for (; x < tmp1->W; ++x) {
                                tmp1->L[y][x] = 32768.f * gammalog(tmp1->L[y][x] / 32768.f, gamma, ts, g_a[3], g_a[4]);
                            }
                        }
                    }

                    const float satur = params->locallab.spots.at(sp).residchro;


                    if (satur != 0.f || radblur > 0.f) {//blur residual a and satur

                        wavelet_decomposition *wdspota = new wavelet_decomposition(tmp1->a[0], tmp1->W, tmp1->H, wavelet_level, 1, sk, numThreads, lp.daubLen);

                        if (wdspota->memory_allocation_failed()) {
                            return;
                        }

                        float *wav_ab0a = wdspota->get_coeff0();
                        //      int maxlvla = wdspota->maxlevel();
                        int W_La = wdspota->level_W(0);
                        int H_La = wdspota->level_H(0);

                        if (radblur > 0.f && !blurlc && blurena) {
                            array2D<float> bufa(W_La, H_La);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < H_La; y++) {
                                for (int x = 0; x < W_La; x++) {
                                    bufa[y][x]  = wav_ab0a [y * W_La + x];
                                }
                            }

#ifdef _OPENMP
                            #pragma omp parallel if (multiThread)
#endif
                            {
                                gaussianBlur(bufa, bufa, W_La, H_La, radblur);
                            }

#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < H_La; y++) {
                                for (int x = 0; x < W_La; x++) {
                                    wav_ab0a[y * W_La + x] = bufa[y][x];
                                }
                            }

                        }

                        if (satur != 0.f) {
#ifdef _OPENMP
                            #pragma omp parallel for if (multiThread)
#endif

                            for (int i = 0; i < W_La * H_La; i++) {
                                wav_ab0a[i] *= (1.f + xsinf(rtengine::RT_PI_F * (satur / 200.f)));//more progressive than linear
                                wav_ab0a[i] = clipC(wav_ab0a[i]);
                            }
                        }

                        wdspota->reconstruct(tmp1->a[0], 1.f);
                        delete wdspota;

                        wavelet_decomposition *wdspotb = new wavelet_decomposition(tmp1->b[0], tmp1->W, tmp1->H, wavelet_level, 1, sk, numThreads, lp.daubLen);

                        if (wdspotb->memory_allocation_failed()) {
                            return;
                        }

                        float *wav_ab0b = wdspotb->get_coeff0();
                        int W_Lb = wdspotb->level_W(0);
                        int H_Lb = wdspotb->level_H(0);

                        if (radblur > 0.f && !blurlc && blurena) {
                            array2D<float> bufb(W_Lb, H_Lb);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < H_Lb; y++) {
                                for (int x = 0; x < W_Lb; x++) {
                                    bufb[y][x]  = wav_ab0b [y * W_Lb + x];
                                }
                            }

#ifdef _OPENMP
                            #pragma omp parallel if (multiThread)
#endif
                            {
                                gaussianBlur(bufb, bufb, W_Lb, H_Lb, radblur);
                            }


#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < H_Lb; y++) {
                                for (int x = 0; x < W_Lb; x++) {
                                    wav_ab0b[y * W_Lb + x] = bufb[y][x];
                                }
                            }

                        }

                        if (satur != 0.f) {

#ifdef _OPENMP
                            #pragma omp parallel for if (multiThread)
#endif

                            for (int i = 0; i < W_Lb * H_Lb; i++) {
                                wav_ab0b[i] *= (1.f + xsinf(rtengine::RT_PI_F * (satur / 200.f)));
                                wav_ab0b[i] = clipC(wav_ab0b[i]);
                            }
                        }

                        wdspotb->reconstruct(tmp1->b[0], 1.f);
                        delete wdspotb;
                    }

                    if (!origlc) {//merge all files
                        exec = false;
                        //copy previous calculation in merge possibilities
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfhr; y++) {
                            for (int x = 0; x < bfwr; x++) {
                                tmpresid->L[y][x] = tmp1->L[y][x];
                                tmpresid->a[y][x] = tmp1->a[y][x];
                                tmpresid->b[y][x] = tmp1->b[y][x];
                            }
                        }

                        clarimerge(lp, mL, mC, exec, tmpresid.get(), wavelet_level, sk, numThreads);
                    }

                    float thr = 0.001f;
                    int flag = 0;

                    if (maxlvl <= 4) {
                        mL0 = 0.f;
                        mC0 = 0.f;
                        mL = -1.5f * mL;//increase only for sharpen
                        mC = -mC;
                        thr = 1.f;
                        flag = 0;

                    } else {
                        mL0 = mL;
                        mC0 = mC;
                        thr = 1.f;
                        flag = 1;
                    }

                    if (exec  || compreena || comprena || levelena || blurena || lp.wavgradl || locwavCurve || lp.edgwena) {
                        LabImage *mergfile = tmp1.get();

#ifdef _OPENMP
                        #pragma omp parallel for if (multiThread)
#endif

                        for (int x = 0; x < bfh; x++)
                            for (int y = 0; y < bfw; y++) {
                                tmp1->L[x][y] = clipLoc((1.f + mL0) * mergfile->L[x][y] - mL * tmpresid->L[x][y]);
                                tmp1->a[x][y] = clipC((1.f + mC0) * mergfile->a[x][y] - mC * tmpresid->a[x][y]);
                                tmp1->b[x][y] = clipC((1.f + mC0) * mergfile->b[x][y] - mC * tmpresid->b[x][y]);
                            }

                        if (softr != 0.f && (compreena || locwavCurve || comprena || blurena || levelena || lp.wavgradl || lp.edgwena || std::fabs(mL) > 0.001f)) {
                            softproc(tmpres.get(), tmp1.get(), softr, bfh, bfw, 0.001, 0.00001, thr, sk, multiThread, flag);
                        }
                    }
                }

                if (lp.enalcMask && lp.recothrw != 1.f) {
                    float recoth = lp.recothrw;

                    if (lp.recothrw < 1.f) {
                        recoth = -1.f * recoth + 2.f;
                    }

                    float hig = lp.higthrw;
                    float low = lp.lowthrw;
                    //float recoth = lp.recothrw;
                    float decay = lp.decayw;
                    bool invmask = false;
                    maskrecov(tmp1.get(), original, bufmaskoriglc.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                }

                const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparw;
                int bw = bufgb->W;
                int bh = bufgb->H;

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                for (int x = 0; x < bh; x++) {
                    for (int y = 0; y < bw; y++) {
                        tmp1->L[x][y] = intp(repart, bufgb->L[x][y], tmp1->L[x][y]);
                        tmp1->a[x][y] = intp(repart, bufgb->a[x][y], tmp1->a[x][y]);
                        tmp1->b[x][y] = intp(repart, bufgb->b[x][y], tmp1->b[x][y]);
                    }
                }

                if (lp.recothrw >= 1.f) {
                    transit_shapedetect2(sp, 0.f, 0.f, call, 10, bufgb.get(), tmp1.get(), originalmasklc.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                } else {
                    transit_shapedetect2(sp, 0.f, 0.f, call, 10, bufgb.get(), tmp1.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                }

                tmp1.reset();
            }

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }

    bool rl = (params->locallab.spots.at(sp).methodcap == "rl");
    bool sharinit = true;
    if(params->locallab.spots.at(sp).sharamount < 1) {//disable all shapening and inverse if amount < 1
        sharinit = false;
    }
    
    if (!lp.invshar && rl && lp.shrad > 0.42 && call < 3 && lp.sharpena && sk == 1  && sharinit) { //interior ellipse for sharpening, call = 1 and 2 only with Dcrop and simpleprocess
        int bfh = call == 2 ? int (lp.ly + lp.lyT) + del : original->H; //bfw bfh real size of square zone
        int bfw = call == 2 ? int (lp.lx + lp.lxL) + del : original->W;

        if (call == 2) { //call from simpleprocess
            if(params->locallab.spots.at(sp).methodcap != "cap") {
                if (bfw < mSPsharp || bfh < mSPsharp) {
                    printf("too small RT-spot - minimum size 39 * 39\n");
                    return;
                }
            } else {
                if (bfw < mSPsharpCS || bfh < mSPsharpCS) {
                    printf("too small RT-spot - minimum size 150 * 150\n");
                    return;
                }
            }    

            int begy = lp.yc - lp.lyT;
            int begx = lp.xc - lp.lxL;
            int yEn = lp.yc + lp.ly;
            int xEn = lp.xc + lp.lx;

            if(lp.fullim >= 2) {//full-iamge and global - limit sharpening to image dimension...no more...to avoid a long treatment
                begy = 0;
                begx = 0;
                yEn = original->H;
                xEn = original->W;
                lp.lxL = lp.xc;
                lp.lyT = lp.yc;
                lp.ly = yEn - lp.yc;
                lp.lx = xEn - lp.xc;
                bfh = yEn;
                bfw = xEn;
            }

            JaggedArray<float> bufsh(bfw, bfh, true);
            JaggedArray<float> hbuffer(bfw, bfh);
            JaggedArray<float> loctemp2(bfw, bfh);

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < transformed->H ; y++) {
                for (int x = 0; x < transformed->W; x++) {
                    int lox = cx + x;
                    int loy = cy + y;

                    if (lox >= begx && lox < xEn && loy >= begy && loy < yEn) {
                        bufsh[loy - begy][lox - begx] = original->L[y][x];
                    }
                }
            }

            float gamma1 = params->locallab.spots.at(sp).shargam;
            rtengine::GammaValues g_a; //gamma parameters
            double pwr1 = 1.0 / (double) gamma1;//default 3.0 - gamma Lab
            double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
            rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

            if (gamma1 != 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh; ++y) {
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < bfw - 3; x += 4) {
                        STVFU(bufsh[y][x], F2V(32768.f) * igammalog(LVFU(bufsh[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                    }

#endif

                    for (; x < bfw; ++x) {
                        bufsh[y][x] = 32768.f * igammalog(bufsh[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                    }
                }
            }


            //sharpen only square area instead of all image, but limited to image dimensions (full image)
                ImProcFunctions::deconvsharpeningloc(bufsh, hbuffer, bfw, bfh, loctemp2, params->locallab.spots.at(sp).shardamping, (double)params->locallab.spots.at(sp).sharradius, params->locallab.spots.at(sp).shariter, params->locallab.spots.at(sp).sharamount, params->locallab.spots.at(sp).sharcontrast, (double)params->locallab.spots.at(sp).sharblur, 1);
            /*
            float gamma =  params->locallab.spots.at(sp).shargam;
            double pwr = 1.0 / (double) gamma;//default 3.0 - gamma Lab
            double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
            rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope
            */
            if (gamma1 != 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < bfw - 3; x += 4) {
                        STVFU(bufsh[y][x], F2V(32768.f) * gammalog(LVFU(bufsh[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                        STVFU(loctemp2[y][x], F2V(32768.f) * gammalog(LVFU(loctemp2[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                    }

#endif

                    for (; x < bfw; ++x) {
                        bufsh[y][x] = 32768.f * gammalog(bufsh[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                        loctemp2[y][x] = 32768.f * gammalog(loctemp2[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                    }
                }
            }

            //sharpen simpleprocess
            Sharp_Local(call, loctemp2, 0, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);
        } else { //call from dcrop.cc
            JaggedArray<float> loctemp(bfw, bfh);

            float gamma1 = params->locallab.spots.at(sp).shargam;
            rtengine::GammaValues g_a; //gamma parameters
            double pwr1 = 1.0 / (double) gamma1;//default 3.0 - gamma Lab
            double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
            rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

            if (gamma1 != 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh; ++y) {
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < bfw - 3; x += 4) {
                        STVFU(original->L[y][x], F2V(32768.f) * igammalog(LVFU(original->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                    }

#endif

                    for (; x < bfw; ++x) {
                        original->L[y][x] = 32768.f * igammalog(original->L[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                    }
                }
            }

            
                ImProcFunctions::deconvsharpeningloc(original->L, shbuffer, bfw, bfh, loctemp, params->locallab.spots.at(sp).shardamping, (double)params->locallab.spots.at(sp).sharradius, params->locallab.spots.at(sp).shariter, params->locallab.spots.at(sp).sharamount, params->locallab.spots.at(sp).sharcontrast, (double)params->locallab.spots.at(sp).sharblur, sk);
           

            /*
            float gamma =  params->locallab.spots.at(sp).shargam;
            double pwr = 1.0 / (double) gamma;//default 3.0 - gamma Lab
            double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
            rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope
            */
            if (gamma1 != 1.f) {
#ifdef _OPENMP
                #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                    int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                    for (; x < bfw - 3; x += 4) {
                        STVFU(original->L[y][x], F2V(32768.f) * gammalog(LVFU(original->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                        STVFU(loctemp[y][x], F2V(32768.f) * gammalog(LVFU(loctemp[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                    }

#endif

                    for (; x < bfw; ++x) {
                        original->L[y][x] = 32768.f * gammalog(original->L[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                        loctemp[y][x] = 32768.f * gammalog(loctemp[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                    }
                }
            }

            //sharpen dcrop
            Sharp_Local(call, loctemp, 0, hueref, chromaref, lumaref, lp, original, transformed, cx, cy, sk);
        }


        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }

    } else if (lp.invshar && rl && lp.shrad > 0.42 && call < 3 && lp.sharpena && sk == 1  && sharinit) {
        int GW = original->W;
        int GH = original->H;
        JaggedArray<float> loctemp(GW, GH);

        float gamma1 = params->locallab.spots.at(sp).shargam;
        rtengine::GammaValues g_a; //gamma parameters
        double pwr1 = 1.0 / (double) gamma1;//default 3.0 - gamma Lab
        double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
        rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

        if (gamma1 != 1.f) {
#ifdef _OPENMP
            #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < GH; ++y) {
                int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                for (; x < GW - 3; x += 4) {
                    STVFU(original->L[y][x], F2V(32768.f) * igammalog(LVFU(original->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                }

#endif

                for (; x < GW; ++x) {
                    original->L[y][x] = 32768.f * igammalog(original->L[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                }
            }
        }


        ImProcFunctions::deconvsharpeningloc(original->L, shbuffer, GW, GH, loctemp, params->locallab.spots.at(sp).shardamping, (double)params->locallab.spots.at(sp).sharradius, params->locallab.spots.at(sp).shariter, params->locallab.spots.at(sp).sharamount, params->locallab.spots.at(sp).sharcontrast, (double)params->locallab.spots.at(sp).sharblur, sk);

        /*
        float gamma =  params->locallab.spots.at(sp).shargam;
        double pwr = 1.0 / (double) gamma;//default 3.0 - gamma Lab
        double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
        rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope
        */
        if (gamma1 != 1.f) {
#ifdef _OPENMP
            #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < GH; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                for (; x < GW - 3; x += 4) {
                    STVFU(original->L[y][x], F2V(32768.f) * gammalog(LVFU(original->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                    STVFU(loctemp[y][x], F2V(32768.f) * gammalog(LVFU(loctemp[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                }

#endif

                for (; x < GW; ++x) {
                    original->L[y][x] = 32768.f * gammalog(original->L[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                    loctemp[y][x] = 32768.f * gammalog(loctemp[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                }
            }
        }


        InverseSharp_Local(loctemp, hueref, lumaref, chromaref, lp, original, transformed, cx, cy, sk);

        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }
    }

//Sharp methodcap Capture deconvolution
    bool cap = params->locallab.spots.at(sp).methodcap == "cap";

    if (!lp.invshar && cap && lp.sharpena) {//  &&  lp.fullim >= 2) {//provisory spot normal not possible (allocation memory ??)
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;

        if (bfw >= mSPsharpCS  && bfh >= mSPsharpCS) {//for buildblendmask
            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> copyorig(new LabImage(original->W, original->H));//copy original image to keep initial datas

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend; y++) {
                for (int x = xstart; x < xend; x++) {
                    bufexporig->L[y - ystart][x - xstart] = original->L[y][x];
                    bufexporig->a[y - ystart][x - xstart] = original->a[y][x];
                    bufexporig->b[y - ystart][x - xstart] = original->b[y][x];
                }
            }

            bufexpfin->CopyFrom(bufexporig.get(), multiThread);
            
            
            float contra = params->locallab.spots.at(sp).sharcontrast;
            float capradiu = params->locallab.spots.at(sp).capradius;
            float deconvCo = params->locallab.spots.at(sp).deconvCoBoost;
            float deconvCopro = 0.01 * params->locallab.spots.at(sp).deconvCoProt;
            float deconvLat = params->locallab.spots.at(sp).deconvCoLat;
            float deconvgam = params->locallab.spots.at(sp).deconvCogam;
            bool autoshar = params->locallab.spots.at(sp).deconvAutoshar;
            bool sharpshow = params->locallab.spots.at(sp).sharshow;
            bool itcheck = params->locallab.spots.at(sp).itercheck;

            const std::unique_ptr<Imagefloat> tmpImagered(new Imagefloat(bfw, bfh));//part of image to be used with Spots
            const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(original->W, original->H));//all image

            if(!autoshar){
               sharc = contra; 
            }
            struct localpass locp;//to pass parameters Spot to Capture Sharpening if nead
                locp.centrx = lp.xc;
                locp.centrx = lp.yc;
                locp.lx = lp.lx;
                locp.ly = lp.ly;
                locp.lxL = lp.lxL;
                locp.lyT = lp.lyT;
                locp.xstart = xstart;
                locp.ystart = ystart;
                locp.xend = xend;
                locp.yend = yend;
                locp.sizenocoboot = deconvCopro;

            lab2rgb(*original, *tmpImage, params->icm.workingProfile);//copy original  image lab to RGB
            
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = ystart; y < yend; y++) {//take only part with Spot
                for (int x = xstart; x < xend; x++) {
                    tmpImagered->r(y - ystart, x - xstart) = tmpImage->r(y,x);
                    tmpImagered->g(y - ystart, x - xstart) = tmpImage->g(y,x);
                    tmpImagered->b(y - ystart, x - xstart) = tmpImage->b(y,x);
                }
            }
              
            ImProcFunctions::doCapture_Sharpening_SE(tmpImagered.get(), bfw, bfh, locp, sk, sharc, autoshar, capradiu,  deconvCo, deconvLat, itcheck, sharpshow, deconvgam);
            
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
            for (int y = ystart; y < yend; y++) {//retrieve all image
                for (int x = xstart; x < xend; x++) {
                    tmpImage->r(y,x) = tmpImagered->r(y - ystart,x - xstart);
                    tmpImage->g(y,x) = tmpImagered->g(y - ystart, x - xstart);
                    tmpImage->b(y,x) = tmpImagered->b(y - ystart, x - xstart);
                }
            }
       
            rgb2lab(*tmpImage, *copyorig, params->icm.workingProfile);//conver all image lo Lab

            const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparsha;
            //take into account overall strength
#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
            for (int y = ystart; y < yend; y++) {//retrieve Lab datas
                for (int x = xstart; x < xend; x++) {
                    bufexpfin->L[y - ystart][x - xstart] = intp(repart, bufexporig->L[y - ystart][x - xstart], copyorig->L[y][x]);
                    bufexpfin->a[y - ystart][x - xstart] = intp(repart, bufexporig->a[y - ystart][x - xstart], copyorig->a[y][x]);
                    bufexpfin->b[y - ystart][x - xstart] = intp(repart, bufexporig->b[y - ystart][x - xstart], copyorig->b[y][x]);
                }
            }
          
            transit_shapedetect2(sp, 0.f, 0.f, call, 99, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
            
        }
    }
    
    
    bool enablefat = false;

    if (params->locallab.spots.at(sp).fatamount > 1.0) {
        enablefat = true;;
    }

    bool execex = (lp.exposena && (lp.expcomp != 0.f || lp.blac != 0 || lp.shadex > 0 || lp.hlcomp > 0.f || lp.laplacexp > 0.1f || lp.strexp != 0.f || enablefat || lp.showmaskexpmet == 2 || lp.enaExpMask || lp.showmaskexpmet == 3 || lp.showmaskexpmet == 4  || lp.showmaskexpmet == 5 || lp.prevdE || (exlocalcurve && localexutili)));

    if (!lp.invex && execex) {
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;
        //variable for fast FFTW
        int bfhr = bfh;
        int bfwr = bfw;


        if (bfw >= mSP && bfh >= mSP) {

            if (lp.expmet == 1  || lp.expmet == 0) {
                optfft(N_fftwsize, bfh, bfw, bfhr, bfwr, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh));
            const std::unique_ptr<LabImage> buforig(new LabImage(bfw, bfh));

            std::unique_ptr<LabImage> bufmaskblurexp;
            std::unique_ptr<LabImage> originalmaskexp;

            array2D<float> blend2;

            if (call <= 3) { //simpleprocess, dcrop, improccoordinator
                if (lp.showmaskexpmet == 2  || lp.enaExpMask || lp.showmaskexpmet == 3 || lp.showmaskexpmet == 5) {
                    bufmaskblurexp.reset(new LabImage(bfw, bfh));
                    originalmaskexp.reset(new LabImage(bfw, bfh));
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                        buforig->a[y][x] = original->a[y + ystart][x + xstart];
                    }
                }

                float gamma1 = lp.gamex;
                rtengine::GammaValues g_a; //gamma parameters
                double pwr1 = 1.0 / (double) lp.gamex;//default 3.0 - gamma Lab
                double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

                if (gamma1 != 1.f) {
#ifdef _OPENMP
                    #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bfh; ++y) {
                        int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x < bfw - 3; x += 4) {
                            STVFU(bufexporig->L[y][x], F2V(32768.f) * igammalog(LVFU(bufexporig->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                        }

#endif

                        for (; x < bfw; ++x) {
                            bufexporig->L[y][x] = 32768.f * igammalog(bufexporig->L[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                        }
                    }
                }

                const int spotSi = rtengine::max(1 + 2 * rtengine::max(1.f, lp.cir / sk), 5.f);

                if (bfw > 2 * spotSi && bfh > 2 * spotSi && lp.struexp > 0.f) {
                    blend2(bfw, bfh);
                    ImProcFunctions::blendstruc(bfw, bfh, bufexporig.get(), 3.f / (sk * 1.4f), 0.5f * lp.struexp, blend2, sk, multiThread);

                    if (lp.showmaskexpmet == 4) {
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = ystart; y < yend ; y++) {
                            for (int x = xstart; x < xend; x++) {
                                const int lox = cx + x;
                                const int loy = cy + y;
                                int zone;
                                float localFactor = 1.f;
                                const float achm = lp.trans / 100.f;

                                if (lp.shapmet == 0) {
                                    calcTransition(lox, loy, achm, lp, zone, localFactor);
                                } else { /*if (lp.shapmet == 1)*/
                                    calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
                                }

                                if (zone > 0) {
                                    transformed->L[y][x] = CLIP(blend2[y - ystart][x - xstart]);
                                    transformed->a[y][x] = 0.f;
                                    transformed->b[y][x] = 0.f;
                                }
                            }
                        }

                        return;
                    }
                }

                int inv = 0;
                bool showmaske = false;
                const bool enaMask = lp.enaExpMask;
                bool deltaE = false;
                bool modmask = false;
                bool zero = false;
                bool modif = false;

                if (lp.showmaskexpmet == 3) {
                    showmaske = true;
                } else if (lp.showmaskexpmet == 5) {
                    deltaE = true;
                } else if (lp.showmaskexpmet == 2) {
                    modmask = true;
                } else if (lp.showmaskexpmet == 1) {
                    modif = true;
                } else if (lp.showmaskexpmet == 0) {
                    zero = true;
                }

                float chrom = lp.chromaexp;
                float rad = lp.radmaexp;
                float gamma = lp.gammaexp;
                float slope = lp.slomaexp;
                float blendm = lp.blendmaexp;
                float lap = params->locallab.spots.at(sp).lapmaskexp;
                bool pde = params->locallab.spots.at(sp).laplac;
                LocwavCurve dummy;
                int sco = params->locallab.spots.at(sp).scopemask;
                int shado = 0;
                int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;

                const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                float amountcd = 0.f;
                float anchorcd = 50.f;
                int lumask = params->locallab.spots.at(sp).lumask;
                LocHHmaskCurve lochhhmasCurve;
                const int highl = 0;
                maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufexporig.get(), bufmaskblurexp.get(), originalmaskexp.get(), original, reserved, inv, lp,
                            0.f, false,
                            locccmasexpCurve, lcmasexputili, locllmasexpCurve, llmasexputili, lochhmasexpCurve, lhmasexputili, lochhhmasCurve, false, multiThread,
                            enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskexplocalcurve, localmaskexputili, dummy, false, 1, 1, 5, 5,
                            shortcu, params->locallab.spots.at(sp).deltae, hueref, chromaref, lumaref,
                            maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, 0, fab, fw, fh, ksk, kskx, kbh, kbw
                           );

                if (lp.showmaskexpmet == 3) {
                    showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufexporig.get(), transformed, bufmaskblurexp.get(), 0);

                    return;
                }

                if (lp.showmaskexpmet == 4) {
                    return;
                }

                if (lp.showmaskexpmet == 0 || lp.showmaskexpmet == 1  || lp.showmaskexpmet == 2  || lp.showmaskexpmet == 5 || lp.enaExpMask) {
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            bufexpfin->L[y][x] = original->L[y + ystart][x + xstart];
                            bufexpfin->a[y][x] = original->a[y + ystart][x + xstart];
                            bufexpfin->b[y][x] = original->b[y + ystart][x + xstart];
                        }
                    }

                    if (exlocalcurve && localexutili) {// L=f(L) curve enhanced

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                bufexpfin->L[ir][jr] = 0.6f * bufexporig->L[ir][jr] + 0.2f * exlocalcurve[2.f * bufexporig->L[ir][jr]];
                            }

                        if (lp.expcomp == 0.f) {
                            lp.expcomp = 0.001f;// to enabled
                        }

                        ImProcFunctions::exlabLocal(lp, 0.5f, bfh, bfw, bfhr, bfwr, bufexpfin.get(), bufexpfin.get(), hltonecurveloc, shtonecurveloc, tonecurveloc, hueref, lumaref, chromaref);



                    } else {
                        if (lp.expcomp == 0.f  && (lp.linear > 0.01f && lp.laplacexp > 0.1f)) {
                            lp.expcomp = 0.001f;// to enabled
                        }

                        if (lp.expcomp != 0.f) {   // ||  lp.laplacexp > 0.1f
                            if (lp.laplacexp <= 0.1f) {
                                lp.laplacexp = 0.2f;  //force to use Laplacian with very small values
                            }

                            ImProcFunctions::exlabLocal(lp, 1.f, bfh, bfw, bfhr, bfwr, bufexporig.get(), bufexpfin.get(), hltonecurveloc, shtonecurveloc, tonecurveloc, hueref, lumaref, chromaref);
                        }
                    }

//gradient
                    struct grad_params gp;

                    if (lp.strexp != 0.f) {

                        calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 1, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++) {
                            for (int jr = 0; jr < bfw; jr++) {
                                bufexpfin->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                            }
                        }
                    }

//exposure_pde
                    if (lp.expmet == 1) {
                        if (enablefat) {

                            const std::unique_ptr<float[]> datain(new float[bfwr * bfhr]);
                            const std::unique_ptr<float[]> dataout(new float[bfwr * bfhr]);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfhr; y++) {
                                for (int x = 0; x < bfwr; x++) {
                                    datain[y * bfwr + x] = bufexpfin->L[y][x];
                                }
                            }

                            FattalToneMappingParams fatParams;
                            fatParams.enabled = true;
                            fatParams.threshold = params->locallab.spots.at(sp).fatdetail;
                            fatParams.amount = params->locallab.spots.at(sp).fatamount;
                            fatParams.anchor = params->locallab.spots.at(sp).fatanchor;
                            //const float sigm = 1.f; //params->locallab.spots.at(sp).fatlevel;
                            //const float mean = 1.f;// params->locallab.spots.at(sp).fatanchor;
                            const std::unique_ptr<Imagefloat> tmpImagefat(new Imagefloat(bfwr, bfhr));
                            lab2rgb(*bufexpfin, *tmpImagefat, params->icm.workingProfile);
                            int alg = 0;

                            if (fatParams.anchor == 50.f) {
                                alg = 1;
                            }
                            bool satu = false;
                            if(params->locallab.spots.at(sp).fatsatur) {
                                satu = true;
                            }
                            ToneMapFattal02(tmpImagefat.get(), fatParams, 3, 0, nullptr, 0, 0, alg, satu);//last parameter alg = 1 ==>ART algorithm
                            rgb2lab(*tmpImagefat, *bufexpfin, params->icm.workingProfile);

                            if (params->locallab.spots.at(sp).expcie && params->locallab.spots.at(sp).modecie == "dr") {
                                bool HHcurvejz = false, CHcurvejz = false, LHcurvejz = false;

                                if (params->locallab.spots.at(sp).modecam == "jz") {//some cam16 elementsfor Jz
                                    ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 10, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                                }

                                ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 0, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);

                                float rad = params->locallab.spots.at(sp).detailcie;
                                loccont(bfw, bfh, bufexpfin.get(), rad, 15.f, sk);
                            }

                        }

                        if (lp.laplacexp > 0.1f) {//don't use if an other spot use Dehaze.
                            MyMutex::MyLock lock(*fftwMutex);
                            std::unique_ptr<float[]> datain(new float[bfwr * bfhr]);
                            std::unique_ptr<float[]> dataout(new float[bfwr * bfhr]);
                            const float gam = params->locallab.spots.at(sp).gamm;
                            const float igam = 1.f / gam;

                            if (params->locallab.spots.at(sp).exnoiseMethod == "med" || params->locallab.spots.at(sp).exnoiseMethod == "medhi") {
                                if (lp.blac < -100.f && lp.linear > 0.01f) {
                                    float evnoise = lp.blac - lp.linear * 2000.f;

                                    if (params->locallab.spots.at(sp).exnoiseMethod == "med") {
                                        evnoise *= 0.4f;
                                    }

                                    //soft denoise, user must use Local Denoise for best result
                                    Median med;

                                    if (evnoise < -18000.f) {
                                        med = Median::TYPE_5X5_STRONG;
                                    } else if (evnoise < -15000.f) {
                                        med = Median::TYPE_5X5_SOFT;
                                    } else if (evnoise < -10000.f) {
                                        med = Median::TYPE_3X3_STRONG;
                                    } else {
                                        med = Median:: TYPE_3X3_SOFT;
                                    }

                                    Median_Denoise(bufexpfin->L, bufexpfin->L, bfwr, bfhr, med, 1, multiThread);
                                    Median_Denoise(bufexpfin->a, bufexpfin->a, bfwr, bfhr, Median::TYPE_3X3_SOFT, 1, multiThread);
                                    Median_Denoise(bufexpfin->b, bufexpfin->b, bfwr, bfhr, Median::TYPE_3X3_SOFT, 1, multiThread);
                                }
                            }

#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfhr; y++) {
                                for (int x = 0; x < bfwr; x++) {
                                    float L = LIM01(bufexpfin->L[y][x] / 32768.f);//change gamma for Laplacian
                                    datain[y * bfwr + x] = pow_F(L, gam) * 32768.f;
                                }
                            }

                            //call PDE equation - with Laplacian threshold
                            ImProcFunctions::exposure_pde(datain.get(), datain.get(), dataout.get(), bfwr, bfhr, 12.f * lp.laplacexp, lp.balanexp);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfhr; y++) {
                                for (int x = 0; x < bfwr; x++) {
                                    const float Y = dataout[y * bfwr + x] / 32768.f;//inverse Laplacian gamma
                                    bufexpfin->L[y][x] = pow_F(Y, igam) * 32768.f;
                                }
                            }
                        }
                    }

                    if (lp.shadex > 0) {

                        if (lp.expcomp == 0.f) {
                            lp.expcomp = 0.001f;    // to enabled
                        }
                    }

                    if (lp.hlcomp > 0.f) {

                        if (lp.expcomp == 0.f) {
                            lp.expcomp = 0.001f;    // to enabled
                        }
                    }

                    //shadows with ipshadowshighlight
                    if ((lp.expcomp != 0.f) || (exlocalcurve && localexutili)) {
                        if (lp.shadex > 0) {

                            ImProcFunctions::shadowsHighlights(bufexpfin.get(), true, 1, 0, lp.shadex, 40, sk, 0, lp.shcomp);
                        }
                    }

                    if (lp.expchroma != 0.f) {
                        if ((lp.expcomp != 0.f && lp.expcomp != 0.001f) || (exlocalcurve && localexutili) || lp.laplacexp > 0.1f) {

                            constexpr float ampli = 70.f;
                            const float ch = (1.f + 0.02f * lp.expchroma);
                            const float chprosl = ch <= 1.f ? 99.f * ch - 99.f : clipChro(ampli * ch - ampli);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++) {
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float epsi = bufexporig->L[ir][jr] == 0.f ? 0.001f : 0.f;
                                    const float rapexp = bufexpfin->L[ir][jr] / (bufexporig->L[ir][jr] + epsi);
                                    bufexpfin->a[ir][jr] *= 1.f + chprosl * rapexp;
                                    bufexpfin->b[ir][jr] *= 1.f + chprosl * rapexp;
                                }
                            }
                        }
                    }

                    /*
                    float gamma = lp.gamex;
                    rtengine::GammaValues g_a; //gamma parameters
                    double pwr = 1.0 / (double) lp.gamex;//default 3.0 - gamma Lab
                    double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                    rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope
                    */
                    if (gamma1 != 1.f) {
#ifdef _OPENMP
                        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                            int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < bfw - 3; x += 4) {
                                STVFU(bufexpfin->L[y][x], F2V(32768.f) * gammalog(LVFU(bufexpfin->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                            }

#endif

                            for (; x < bfw; ++x) {
                                bufexpfin->L[y][x] = 32768.f * gammalog(bufexpfin->L[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                            }
                        }
                    }

                    if (lp.softradiusexp > 0.f && lp.expmet == 0) {
                        softproc(buforig.get(), bufexpfin.get(), lp.softradiusexp, bfh, bfw, 0.1, 0.001, 0.5f, sk, multiThread, 1);
                    }

                    if (lp.enaExpMask && lp.recothre != 1.f) {
                        float recoth = lp.recothre;

                        if (lp.recothre < 1.f) {
                            recoth = -1.f * recoth + 2.f;
                        }

                        float hig = lp.higthre;
                        float low = lp.lowthre;
                        //  float recoth = lp.recothre;
                        float decay = lp.decaye;
                        bool invmask = false;
                        maskrecov(bufexpfin.get(), original, bufmaskblurexp.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                    }

                    float meansob = 0.f;

                    const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparexp;
                    int bw = bufexporig->W;
                    int bh = bufexporig->H;

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                    for (int x = 0; x < bh; x++) {
                        for (int y = 0; y < bw; y++) {
                            bufexpfin->L[x][y] = intp(repart, bufexporig->L[x][y], bufexpfin->L[x][y]);
                            bufexpfin->a[x][y] = intp(repart, bufexporig->a[x][y], bufexpfin->a[x][y]);
                            bufexpfin->b[x][y] = intp(repart, bufexporig->b[x][y], bufexpfin->b[x][y]);
                        }
                    }

                    if (lp.recothre >= 1.f) {
                        transit_shapedetect2(sp, 0.f, 0.f, call, 1, bufexporig.get(), bufexpfin.get(), originalmaskexp.get(), hueref, chromaref, lumaref, sobelref, meansob, blend2, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                    } else {
                        transit_shapedetect2(sp, 0.f, 0.f, call, 1, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, meansob, blend2, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                    }
                }

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }
            }
        }
    }
//inverse

    else if (lp.invex && (lp.expcomp != 0.f  || lp.laplacexp > 0.1f || lp.blac != 0 || lp.hlcomp > 0.f || lp.shadex > 0 || params->locallab.spots.at(sp).fatamount > 1.0 || (exlocalcurve && localexutili) || lp.enaExpMaskinv || lp.showmaskexpmetinv == 1) && lp.exposena) {
        constexpr float adjustr = 2.f;
        std::unique_ptr<LabImage> bufmaskblurexp;
        std::unique_ptr<LabImage> originalmaskexp;
        const std::unique_ptr<LabImage> bufexporig(new LabImage(TW, TH));

        if (lp.enaExpMaskinv || lp.showmaskexpmetinv == 1) {
            bufmaskblurexp.reset(new LabImage(TW, TH, true));
            originalmaskexp.reset(new LabImage(TW, TH));
        }

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < TH ; y++) {
            for (int x = 0; x < TW; x++) {
                bufexporig->L[y][x] = original->L[y][x];
            }
        }

        constexpr int inv = 1;
        const bool showmaske = lp.showmaskexpmetinv == 1;
        const bool enaMask = lp.enaExpMaskinv;
        constexpr bool deltaE = false;
        constexpr bool modmask = false;
        const bool zero = lp.showmaskexpmetinv == 0;
        constexpr bool modif = false;
        const float chrom = lp.chromaexp;
        const float rad = lp.radmaexp;
        const float gamma = lp.gammaexp;
        const float slope = lp.slomaexp;
        const float blendm = lp.blendmaexp;
        const float lap = params->locallab.spots.at(sp).lapmaskexp;
        const bool pde = params->locallab.spots.at(sp).laplac;
        LocwavCurve dummy;
        const int sco = params->locallab.spots.at(sp).scopemask;
        constexpr int shado = 0;
        constexpr int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;
        const int lumask = params->locallab.spots.at(sp).lumask;

        const float mindE = 2.f + MINSCOPE * sco * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
        constexpr float amountcd = 0.f;
        constexpr float anchorcd = 50.f;
        LocHHmaskCurve lochhhmasCurve;
        const int highl = 0;
        maskcalccol(call, false, pde, TW, TH, oW, oH, tX, tY, tW, tH, 0, 0, 0, 0, sk, cx, cy, bufexporig.get(), bufmaskblurexp.get(), originalmaskexp.get(), original, reserved, inv, lp,
                    0.f, false,
                    locccmasexpCurve, lcmasexputili, locllmasexpCurve, llmasexputili, lochhmasexpCurve, lhmasexputili, lochhhmasCurve, false, multiThread,
                    enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskexplocalcurve, localmaskexputili, dummy, false, 1, 1, 5, 5,
                    shortcu, false, hueref, chromaref, lumaref,
                    maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, false, 0.f, 0.f, 0, fab, fw, fh, ksk, kskx, kbh, kbw
                   );

        if (lp.showmaskexpmetinv == 1) {
            showmask(lumask, lp, 0, 0, cx, cy, TW, TH, bufexporig.get(), transformed, bufmaskblurexp.get(), inv);
            return;
        }

        if (lp.shadex > 0) {
            if (lp.expcomp == 0.f) {
                lp.expcomp = 0.001f;    // to enabled
            }
        }

        if (lp.hlcomp > 0.f) {
            if (lp.expcomp == 0.f) {
                lp.expcomp = 0.001f;    // to enabled
            }
        }

        InverseColorLight_Local(false, false, sp, 1, lp, originalmaskexp.get(), lightCurveloc, hltonecurveloc, shtonecurveloc, tonecurveloc, exlocalcurve, cclocalcurve, adjustr, localcutili, lllocalcurve, locallutili, original, transformed, cx, cy, hueref, chromaref, lumaref, sk);

        if (lp.recur) {
            original->CopyFrom(transformed, multiThread);
            float avge;
            calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
        }
    }

//local color and light
    const float factor = LocallabParams::LABGRIDL_CORR_MAX * 3.276;
    const float scaling = LocallabParams::LABGRIDL_CORR_SCALE;
    const float scaledirect = LocallabParams::LABGRIDL_DIRECT_SCALE;
    const float a_scale = (lp.highA - lp.lowA) / factor / scaling;
    const float a_base = lp.lowA / scaling;
    const float b_scale = (lp.highB - lp.lowB) / factor / scaling;
    const float b_base = lp.lowB / scaling;
    const bool ctoning = (a_scale != 0.f || b_scale != 0.f || a_base != 0.f || b_base != 0.f);
    const float a_scalemerg = (lp.highAmerg - lp.lowAmerg) / factor / scaling;
    const float b_scalemerg = (lp.highBmerg - lp.lowBmerg) / factor / scaling;

    if (!lp.inv && (lp.chro != 0 || lp.ligh != 0.f || lp.cont != 0 || ctoning || lp.mergemet > 0 ||  lp.strcol != 0.f ||  lp.strcolab != 0.f || lp.qualcurvemet != 0 || lp.showmaskcolmet == 2 || lp.enaColorMask || lp.showmaskcolmet == 3  || lp.showmaskcolmet == 4 || lp.showmaskcolmet == 5 || lp.prevdE) && lp.colorena) { // || lllocalcurve)) { //interior ellipse reinforced lightness and chroma  //locallutili
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;
        const bool spez = params->locallab.spots.at(sp).special;

        if (bfw >= mSP && bfh >= mSP) {

            if (lp.blurcolmask >= 0.25f && lp.fftColorMask && call == 2) {
                optfft(N_fftwsize, bfh, bfw, bfh, bfw, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            std::unique_ptr<LabImage> bufcolorig;
            std::unique_ptr<LabImage> bufcolfin;
            std::unique_ptr<LabImage> bufmaskblurcol;
            std::unique_ptr<LabImage> originalmaskcol;
            std::unique_ptr<LabImage> bufcolreserv;
            std::unique_ptr<LabImage> buftemp;
            array2D<float> blend2;

            float adjustr = 1.0f;

            //adapt chroma to working profile
            if (params->icm.workingProfile == "ProPhoto")   {
                adjustr = 1.2f;   // 1.2 instead 1.0 because it's very rare to have C>170..
            } else if (params->icm.workingProfile == "Adobe RGB")  {
                adjustr = 1.8f;
            } else if (params->icm.workingProfile == "sRGB")       {
                adjustr = 2.0f;
            } else if (params->icm.workingProfile == "WideGamut")  {
                adjustr = 1.2f;
            } else if (params->icm.workingProfile == "Beta RGB")   {
                adjustr = 1.4f;
            } else if (params->icm.workingProfile == "BestRGB")    {
                adjustr = 1.4f;
            } else if (params->icm.workingProfile == "BruceRGB")   {
                adjustr = 1.8f;
            }

            if (call <= 3) { //simpleprocess, dcrop, improccoordinator
                bufcolorig.reset(new LabImage(bfw, bfh));
                bufcolfin.reset(new LabImage(bfw, bfh));
                buftemp.reset(new LabImage(bfw, bfh));

                if (lp.showmaskcolmet == 2  || lp.enaColorMask || lp.showmaskcolmet == 3 || lp.showmaskcolmet == 5) {
                    bufmaskblurcol.reset(new LabImage(bfw, bfh, true));
                    originalmaskcol.reset(new LabImage(bfw, bfh));
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufcolorig->L[y][x] = original->L[y + ystart][x + xstart];
                        bufcolorig->a[y][x] = original->a[y + ystart][x + xstart];
                        bufcolorig->b[y][x] = original->b[y + ystart][x + xstart];
                        bufcolfin->L[y][x] = original->L[y + ystart][x + xstart];
                        bufcolfin->a[y][x] = original->a[y + ystart][x + xstart];
                        bufcolfin->b[y][x] = original->b[y + ystart][x + xstart];
                        buftemp->L[y][x] = original->L[y + ystart][x + xstart];
                        buftemp->a[y][x] = original->a[y + ystart][x + xstart];
                        buftemp->b[y][x] = original->b[y + ystart][x + xstart];
                    }
                }

                float gamma1 = lp.gamc;
                rtengine::GammaValues g_a; //gamma parameters
                double pwr1 = 1.0 / (double) lp.gamc;//default 3.0 - gamma Lab
                double ts1 = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                rtengine::Color::calcGamma(pwr1, ts1, g_a); // call to calcGamma with selected gamma and slope

                if (gamma1 != 1.f) {
#ifdef _OPENMP
                    #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bufcolorig->H; ++y) {
                        int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                        for (; x < bufcolorig->W - 3; x += 4) {
                            STVFU(bufcolorig->L[y][x], F2V(32768.f) * igammalog(LVFU(bufcolorig->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[2]), F2V(g_a[4])));
                        }

#endif

                        for (; x < bufcolorig->W; ++x) {
                            bufcolorig->L[y][x] = 32768.f * igammalog(bufcolorig->L[y][x] / 32768.f, gamma1, ts1, g_a[2], g_a[4]);
                        }
                    }
                }

                const int spotSi = rtengine::max(1 + 2 * rtengine::max(1.f,  lp.cir / sk), 5.f);
                const bool blends = bfw > 2 * spotSi && bfh > 2 * spotSi && lp.struco > 0.f;

                if (blends) {
                    blend2(bfw, bfh);
                    ImProcFunctions::blendstruc(bfw, bfh, bufcolorig.get(), 3.f / (sk * 1.4f), 0.5f * lp.struco, blend2, sk, multiThread);

                    if (lp.showmaskcolmet == 4) {
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = ystart; y < yend ; y++) {
                            for (int x = xstart; x < xend; x++) {
                                const int lox = cx + x;
                                const int loy = cy + y;
                                int zone;
                                float localFactor = 1.f;
                                const float achm = lp.trans / 100.f;

                                if (lp.shapmet == 0) {
                                    calcTransition(lox, loy, achm, lp, zone, localFactor);
                                } else { /*if (lp.shapmet == 1)*/
                                    calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
                                }

                                if (zone > 0) {
                                    transformed->L[y][x] = CLIP(blend2[y - ystart][x - xstart]);
                                    transformed->a[y][x] = 0.f;
                                    transformed->b[y][x] = 0.f;
                                }
                            }
                        }

                        return;
                    }
                }

                const int inv = 0;
                const bool showmaske = lp.showmaskcolmet == 3;
                const bool enaMask = lp.enaColorMask;
                const bool deltaE = lp.showmaskcolmet == 5;
                const bool modmask = lp.showmaskcolmet == 2;
                const bool zero = lp.showmaskcolmet == 0;
                const bool modif = lp.showmaskcolmet == 1;
                const float chrom = lp.chromacol;
                const float rad = lp.radmacol;
                const float gamma = lp.gammacol;
                const float slope = lp.slomacol;
                const float blendm = lp.blendmacol;
                const float lap = params->locallab.spots.at(sp).lapmaskcol;
                const bool pde = params->locallab.spots.at(sp).laplac;
                const int shado = params->locallab.spots.at(sp).shadmaskcol;
                const int sco = params->locallab.spots.at(sp).scopemask;
                const int level_bl = params->locallab.spots.at(sp).csthresholdcol.getBottomLeft();
                const int level_hl = params->locallab.spots.at(sp).csthresholdcol.getTopLeft();
                const int level_br = params->locallab.spots.at(sp).csthresholdcol.getBottomRight();
                const int level_hr = params->locallab.spots.at(sp).csthresholdcol.getTopRight();
                const int shortcu = lp.mergemet; //params->locallab.spots.at(sp).shortc;
                const int lumask = params->locallab.spots.at(sp).lumask;
                const float strumask = 0.02 * params->locallab.spots.at(sp).strumaskcol;
                float conthr = 0.01 * params->locallab.spots.at(sp).conthrcol;
                const float mercol = params->locallab.spots.at(sp).mercol;
                const float merlucol = params->locallab.spots.at(sp).merlucol;

                int tonemod = 0;

                if (params->locallab.spots.at(sp).toneMethod == "one") {
                    tonemod = 0;
                } else if (params->locallab.spots.at(sp).toneMethod == "two") {
                    tonemod = 1;
                } else if (params->locallab.spots.at(sp).toneMethod == "thr") {
                    tonemod = 2;
                } else if (params->locallab.spots.at(sp).toneMethod == "fou") {
                    tonemod = 3;
                }

                const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                const float amountcd = 0.f;
                const float anchorcd = 50.f;
                const int highl = 0;
                bool astool = params->locallab.spots.at(sp).toolcol;
                maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufcolorig.get(), bufmaskblurcol.get(), originalmaskcol.get(), original, reserved, inv, lp,
                            strumask, astool,
                            locccmasCurve, lcmasutili, locllmasCurve, llmasutili, lochhmasCurve, lhmasutili, llochhhmasCurve, lhhmasutili, multiThread,
                            enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmasklocalcurve, localmaskutili, loclmasCurvecolwav, lmasutilicolwav,
                            level_bl, level_hl, level_br, level_hr,
                            shortcu, delt, hueref, chromaref, lumaref,
                            maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, lp.fftColorMask, lp.blurcolmask, lp.contcolmask, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                           );

                if (lp.showmaskcolmet == 3) {
                    showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufcolorig.get(), transformed, bufmaskblurcol.get(), 0);
                    return;
                } else if (lp.showmaskcolmet == 4) {
                    return;
                }

                if (lp.showmaskcolmet == 0 || lp.showmaskcolmet == 1 || lp.showmaskcolmet == 2 || lp.showmaskcolmet == 5 || lp.enaColorMask) {
                    //RGB Curves
                    bool usergb = false;

                    if (rgblocalcurve && localrgbutili && lp.qualcurvemet != 0) {
                        usergb = true;
                        const std::unique_ptr<Imagefloat> tmpImage(new Imagefloat(bfw, bfh));

                        lab2rgb(*buftemp, *tmpImage, params->icm.workingProfile);
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; y++)
                            for (int x = 0; x < bfw; x++) {

                                //std
                                if (tonemod == 0) {
                                    curves::setLutVal(rgblocalcurve, tmpImage->r(y, x), tmpImage->g(y, x), tmpImage->b(y, x));
                                } else {
                                    float r = CLIP(tmpImage->r(y, x));
                                    float g = CLIP(tmpImage->g(y, x));
                                    float b = CLIP(tmpImage->b(y, x));

                                    if (tonemod == 1) { // weightstd
                                        const float r1 = rgblocalcurve[r];
                                        const float g1 = triangle(r, r1, g);
                                        const float b1 = triangle(r, r1, b);

                                        const float g2 = rgblocalcurve[g];
                                        const float r2 = triangle(g, g2, r);
                                        const float b2 = triangle(g, g2, b);

                                        const float b3 = rgblocalcurve[b];
                                        const float r3 = triangle(b, b3, r);
                                        const float g3 = triangle(b, b3, g);
                                        r = CLIP(r1 * 0.50f + r2 * 0.25f + r3 * 0.25f);
                                        g = CLIP(g1 * 0.25f + g2 * 0.50f + g3 * 0.25f);
                                        b = CLIP(b1 * 0.25f + b2 * 0.25f + b3 * 0.50f);
                                    } else if (tonemod == 2) { // Luminance
                                        float currLuminance = r * 0.2126729f + g * 0.7151521f + b * 0.0721750f;

                                        const float newLuminance = rgblocalcurve[currLuminance];
                                        currLuminance = currLuminance == 0.f ? 0.00001f : currLuminance;
                                        const float coef = newLuminance / currLuminance;
                                        r = LIM(r * coef, 0.f, 65535.f);
                                        g = LIM(g * coef, 0.f, 65535.f);
                                        b = LIM(b * coef, 0.f, 65535.f);
                                    } else if (tonemod == 3) { // Film like Adobe
                                        if (r >= g) {
                                            if (g > b) {
                                                rgbtone(r, g, b, rgblocalcurve);     // Case 1: r >= g >  b
                                            } else if (b > r) {
                                                rgbtone(b, r, g, rgblocalcurve);     // Case 2: b >  r >= g
                                            } else if (b > g) {
                                                rgbtone(r, b, g, rgblocalcurve);     // Case 3: r >= b >  g
                                            } else {                           // Case 4: r == g == b
                                                r = rgblocalcurve[r];
                                                g = rgblocalcurve[g];
                                                b = g;
                                            }
                                        } else {
                                            if (r >= b) {
                                                rgbtone(g, r, b, rgblocalcurve);     // Case 5: g >  r >= b
                                            } else if (b >  g) {
                                                rgbtone(b, g, r, rgblocalcurve);     // Case 6: b >  g >  r
                                            } else {
                                                rgbtone(g, b, r, rgblocalcurve);     // Case 7: g >= b >  r
                                            }
                                        }
                                    }

                                    setUnlessOOG(tmpImage->r(y, x), tmpImage->g(y, x), tmpImage->b(y, x), r, g, b);
                                }
                            }

                        rgb2lab(*tmpImage, *buftemp, params->icm.workingProfile);

                        // end rgb curves
                    }

                    if (usergb && spez) {//special use of rgb curves ex : negative
                        const float achm = lp.trans / 100.f;
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; y++) {
                            const int loy = y + ystart + cy;

                            for (int x = 0; x < bfw; x++) {
                                const int lox = x + xstart + cx;
                                int zone;
                                float localFactor = 1.f;

                                if (lp.shapmet == 0) {
                                    calcTransition(lox, loy, achm, lp, zone, localFactor);
                                } else { /*if (lp.shapmet == 1)*/
                                    calcTransitionrect(lox, loy, achm, lp, zone, localFactor);
                                }

                                if (zone > 0) {
                                    transformed->L[y + ystart][x + xstart] = buftemp->L[y][x] * localFactor + (1.f - localFactor) * original->L[y + ystart][x + xstart];
                                    transformed->a[y + ystart][x + xstart] = buftemp->a[y][x] * localFactor + (1.f - localFactor) * original->a[y + ystart][x + xstart];
                                    transformed->b[y + ystart][x + xstart] = buftemp->b[y][x] * localFactor + (1.f - localFactor) * original->b[y + ystart][x + xstart];
                                }
                            }
                        }
                    }

                    //others curves

                    const LabImage *origptr = usergb ? buftemp.get() : bufcolorig.get();

                    bool execcolor = false;

                    if (localcutili || HHutili || locallutili || lp.ligh != 0.f || lp.cont != 0 || lp.chro != 0 || LHutili || ctoning) {
                        execcolor = true;
                    }

                    bool HHcurve = false;

                    if (lochhCurve && HHutili) {
                        for (int i = 0; i < 500; i++) {
                            if (lochhCurve[i] != 0.5f) {
                                HHcurve = true;
                                break;
                            }
                        }
                    }

                    const float kd = 10.f * 0.01f * lp.strengrid;//correction to ctoning

                    //chroma slider with curve instead of linear
                    const float satreal = lp.chro;

                    DiagonalCurve color_satur({
                        DCT_NURBS,
                        0, 0,
                        0.2, 0.2f + satreal / 250.f,
                        0.6, rtengine::min(1.f, 0.6f + satreal / 250.f),
                        1, 1
                    });

                    DiagonalCurve color_saturmoins({
                        DCT_NURBS,
                        0, 0,
                        0.1f - satreal / 150.f, 0.1f,
                        rtengine::min(1.f, 0.7f - satreal / 300.f), 0.7,
                        1, 1
                    });
                    bool LHcurve = false;

                    if (loclhCurve && LHutili) {
                        for (int i = 0; i < 500; i++) {
                            if (loclhCurve[i] != 0.5f) {
                                LHcurve = true;
                                break;
                            }
                        }
                    }

                    bool CHcurve = false;

                    if (locchCurve && CHutili) {
                        for (int i = 0; i < 500; i++) {
                            if (locchCurve[i] != 0.5f) {
                                CHcurve = true;
                                break;
                            }
                        }
                    }

                    double amountchrom = 0.01 * settings->amchroma;

                    if (amountchrom < 0.05) {
                        amountchrom = 0.05;
                    }

                    if (amountchrom > 2.) {
                        amountchrom = 2.;
                    }

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            float bufcolcalca = origptr->a[ir][jr];
                            float bufcolcalcb = origptr->b[ir][jr];
                            float bufcolcalcL = origptr->L[ir][jr];

                            if (lp.chro != 0.f) {//slider chroma with curve DCT_NURBS
                                float Chprov = std::sqrt(SQR(bufcolcalca) + SQR(bufcolcalcb));
                                float2 sincosval;
                                sincosval.y = Chprov == 0.0f ? 1.f : bufcolcalca / Chprov;
                                sincosval.x = Chprov == 0.0f ? 0.f : bufcolcalcb / Chprov;

                                // 35000 must be globally good, more than 32768...and less than !! to avoid calculation min max
                                if (lp.chro > 0.f) {
                                    Chprov = static_cast<float>(color_satur.getVal(LIM01(Chprov / 35000.f))) * 35000.f;
                                } else {
                                    Chprov = static_cast<float>(color_saturmoins.getVal(LIM01(Chprov / 35000.f))) * 35000.f;
                                }

                                if (lp.chro == -100.f) {
                                    Chprov = 0.f;
                                }

                                bufcolcalca = Chprov * sincosval.y;
                                bufcolcalcb = Chprov * sincosval.x;
                            }

                            if (cclocalcurve && lp.qualcurvemet != 0 && localcutili) { // C=f(C) curve
                                const float chromat = std::sqrt(SQR(bufcolcalca) + SQR(bufcolcalcb));
                                const float ch = cclocalcurve[chromat * adjustr] / ((chromat + 0.00001f) * adjustr); //ch between 0 and 0 50 or more
                                bufcolcalca *= ch;
                                bufcolcalcb *= ch;
                            }

                            if (cllocalcurve && lp.qualcurvemet != 0 && localclutili) { // C=f(L) curve
                                float chromaCfactor = (cllocalcurve[bufcolcalcL * 2.f]) / (bufcolcalcL * 2.f);
                                bufcolcalca *= chromaCfactor;
                                bufcolcalcb *= chromaCfactor;
                            }

                            if (lclocalcurve && lp.qualcurvemet != 0 && locallcutili) { // L=f(C) curve
                                const float chromat = std::sqrt(SQR(bufcolcalca) + SQR(bufcolcalcb));
                                float Lc = lclocalcurve[chromat * adjustr] / ((chromat + 0.00001f) * adjustr);

                                if (Lc > 1.f) {
                                    Lc = (Lc - 1.0f) * 0.1f + 1.0f; //reduct action
                                } else {
                                    Lc = (Lc - 1.0f) * 0.3f + 1.0f;
                                }

                                bufcolcalcL *= Lc;
                            }

                            if (lochhCurve && HHcurve && lp.qualcurvemet != 0 && !ctoning) { // H=f(H)
                                const float chromat = std::sqrt(SQR(bufcolcalca) + SQR(bufcolcalcb));
                                const float hhforcurv = xatan2f(bufcolcalcb, bufcolcalca);
                                const float valparam = 2.f * (lochhCurve[500.f * static_cast<float>(Color::huelab_to_huehsv2(hhforcurv))] - 0.5f) + hhforcurv;
                                float2 sincosval = xsincosf(valparam);
                                bufcolcalca = chromat * sincosval.y;
                                bufcolcalcb = chromat * sincosval.x;
                            }

                            if (lp.ligh != 0.f || lp.cont != 0) {//slider luminance or slider contrast with curve
                                bufcolcalcL = calclight(bufcolcalcL, lightCurveloc);
                            }

                            if (lllocalcurve && locallutili && lp.qualcurvemet != 0) {// L=f(L) curve
                                bufcolcalcL = 0.5f * lllocalcurve[bufcolcalcL * 2.f];
                            }


                            if (loclhCurve && LHcurve && lp.qualcurvemet != 0) {//L=f(H) curve
                                const float rhue = xatan2f(bufcolcalcb, bufcolcalca);
                                const float chromat = (std::sqrt(SQR(bufcolcalca) + SQR(bufcolcalcb))) / 32768.f;
                                float l_r = LIM01(bufcolcalcL / 32768.f); //Luminance Lab in 0..1
                                float valparam = loclhCurve[500.f * static_cast<float>(Color::huelab_to_huehsv2(rhue))] - 0.5f; //get l_r=f(H)
                                float kc = amountchrom;
                                float valparamneg;
                                valparamneg = valparam;
                                float kcc = SQR(chromat / kc); //take Chroma into account...40 "middle low" of chromaticity (arbitrary and simple), one can imagine other algorithme
                                //reduce action for low chroma and increase action for high chroma
                                valparam *= 2.f * kcc;
                                valparamneg *= kcc; //slightly different for negative

                                if (valparam > 0.f) {
                                    l_r = (1.f - valparam) * l_r + valparam * (1.f - SQR(((SQR(1.f - min(l_r, 1.0f))))));
                                } else
                                    //for negative
                                {
                                    float khue = 1.9f; //in reserve in case of!
                                    l_r *= (1.f + khue * valparamneg);
                                }

                                bufcolcalcL = l_r * 32768.f;

                            }

                            if (locchCurve && CHcurve && lp.qualcurvemet != 0) {//C=f(H) curve
                                const float rhue = xatan2f(bufcolcalcb, bufcolcalca);
                                const float valparam = locchCurve[500.f * static_cast<float>(Color::huelab_to_huehsv2(rhue))] - 0.5f;  //get valp=f(H)
                                float chromaChfactor = 1.0f + valparam;
                                bufcolcalca *= chromaChfactor;//apply C=f(H)
                                bufcolcalcb *= chromaChfactor;
                            }

                            if (ctoning) {//color toning and direct change color
                                if (lp.gridmet == 0) {
                                    bufcolcalca += kd * (bufcolcalcL * a_scale + a_base);
                                    bufcolcalcb += kd * (bufcolcalcL * b_scale + b_base);
                                } else if (lp.gridmet == 1) {
                                    bufcolcalca += kd * scaledirect * a_scale;
                                    bufcolcalcb += kd * scaledirect * b_scale;
                                }

                                bufcolcalca = clipC(bufcolcalca);
                                bufcolcalcb = clipC(bufcolcalcb);

                            }

                            bufcolfin->L[ir][jr] = bufcolcalcL;
                            bufcolfin->a[ir][jr] = bufcolcalca;
                            bufcolfin->b[ir][jr] = bufcolcalcb;
                        }
                    }

                    if (!execcolor) {//if we don't use color and light sliders, curves except RGB
#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int ir = 0; ir < bfh; ir++)
                            for (int jr = 0; jr < bfw; jr++) {
                                bufcolfin->L[ir][jr] = origptr->L[ir][jr];
                                bufcolfin->a[ir][jr] = origptr->a[ir][jr];
                                bufcolfin->b[ir][jr] = origptr->b[ir][jr];
                            }
                    }

                    bool nottransit = false;

                    if (lp.mergemet >= 2) { //merge result with original
                        nottransit = true;
                        bufcolreserv.reset(new LabImage(bfw, bfh));
                        JaggedArray<float> lumreserv(bfw, bfh);
                        const std::unique_ptr<LabImage> bufreser(new LabImage(bfw, bfh));

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh ; y++) {
                            for (int x = 0; x < bfw; x++) {
                                lumreserv[y][x] = 32768.f - reserved->L[y + ystart][x + xstart];
                                bufreser->L[y][x] = reserved->L[y + ystart][x + xstart];
                                bufreser->a[y][x] = reserved->a[y + ystart][x + xstart];
                                bufreser->b[y][x] = reserved->b[y + ystart][x + xstart];

                                if (lp.mergemet == 2) {
                                    bufcolreserv->L[y][x] = reserved->L[y + ystart][x + xstart];
                                    bufcolreserv->a[y][x] = reserved->a[y + ystart][x + xstart];
                                    bufcolreserv->b[y][x] = reserved->b[y + ystart][x + xstart];
                                } else if (lp.mergemet == 3) {
                                    bufcolreserv->L[y][x] = lastorig->L[y + ystart][x + xstart];
                                    bufcolreserv->a[y][x] = lastorig->a[y + ystart][x + xstart];
                                    bufcolreserv->b[y][x] = lastorig->b[y + ystart][x + xstart];
                                } else if (lp.mergemet == 4) {
                                    bufcolreserv->L[y][x] = merlucol * 327.68f;
                                    bufcolreserv->a[y][x] = 9.f * scaledirect * a_scalemerg;
                                    bufcolreserv->b[y][x] = 9.f * scaledirect * b_scalemerg;
                                }
                            }
                        }

                        if (lp.strcol != 0.f) {
                            struct grad_params gp;
                            calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 3, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++) {
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    bufcolfin->L[ir][jr] *= corrFactor;
                                }
                            }
                        }

                        if (lp.strcolab != 0.f) {
                            struct grad_params gpab;
                            calclocalGradientParams(call, lp, gpab, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 4, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++) {
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gpab, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    bufcolfin->a[ir][jr] *= corrFactor;
                                    bufcolfin->b[ir][jr] *= corrFactor;
                                }
                            }
                        }

                        if (lp.strcolh != 0.f) {
                            struct grad_params gph;
                            calclocalGradientParams(call, lp, gph, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 6, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++) {
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gph, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    const float aa = bufcolfin->a[ir][jr];
                                    const float bb = bufcolfin->b[ir][jr];
                                    const float chrm = std::sqrt(SQR(aa) + SQR(bb));
                                    const float HH = xatan2f(bb, aa);

                                    float cor = 0.f;

                                    if (corrFactor < 1.f) {
                                        cor = - 2.5f * (1.f - corrFactor);
                                    } else if (corrFactor > 1.f) {
                                        cor = 0.03f * (corrFactor - 1.f);
                                    }

                                    float newhr = HH + cor;

                                    if (newhr > rtengine::RT_PI_F) {
                                        newhr -= 2 * rtengine::RT_PI_F;
                                    } else if (newhr < -rtengine::RT_PI_F) {
                                        newhr += 2 * rtengine::RT_PI_F;
                                    }

                                    const float2 sincosval = xsincosf(newhr);
                                    bufcolfin->a[ir][jr] = clipC(chrm * sincosval.y);
                                    bufcolfin->b[ir][jr] = clipC(chrm * sincosval.x);
                                }
                            }
                        }

                        JaggedArray<float> blend(bfw, bfh);
                        buildBlendMask(lumreserv, blend, bfw, bfh, conthr);
                        const float rm = 20.f / sk;

                        if (rm > 0) {
                            float **mb = blend;
#ifdef _OPENMP
                            #pragma omp parallel if (multiThread)
#endif
                            {
                                gaussianBlur(mb, mb, bfw, bfh, rm);
                            }
                        }

                        const std::unique_ptr<JaggedArray<float>> rdEBuffer(new JaggedArray<float>(bfw, bfh));
                        float** rdE = *rdEBuffer;

                        deltaEforMask(rdE, bfw, bfh, bufreser.get(), hueref, chromaref, lumaref, maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, mercol, lp.balance, lp.balanceh);

                        if (lp.mergecolMethod == 0) {  //normal

                            if (lp.mergemet == 4) {
                                bufprov.reset(new LabImage(bfw, bfh));

#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        rdE[y][x] *= SQR(rdE[y][x]);
                                        bufprov->L[y][x] = intp(rdE[y][x], bufcolreserv->L[y][x], bufcolfin->L[y][x]);
                                        bufprov->a[y][x] = intp(rdE[y][x], bufcolreserv->a[y][x], bufcolfin->a[y][x]);
                                        bufprov->b[y][x] = intp(rdE[y][x], bufcolreserv->b[y][x], bufcolfin->b[y][x]);

                                        bufcolfin->L[y][x] = intp(lp.opacol, bufprov->L[y][x], bufcolfin->L[y][x]);
                                        bufcolfin->a[y][x] = intp(lp.opacol, bufprov->a[y][x], bufcolfin->a[y][x]);
                                        bufcolfin->b[y][x] = intp(lp.opacol, bufprov->b[y][x], bufcolfin->b[y][x]);
                                    }
                                }
                            } else {
                                bufprov.reset(new LabImage(bfw, bfh));

#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        bufprov->L[y][x] = intp(rdE[y][x], bufcolfin->L[y][x], bufcolreserv->L[y][x]);
                                        bufprov->a[y][x] = intp(rdE[y][x], bufcolfin->a[y][x], bufcolreserv->a[y][x]);
                                        bufprov->b[y][x] = intp(rdE[y][x], bufcolfin->b[y][x], bufcolreserv->b[y][x]);

                                        bufcolfin->L[y][x] = intp(lp.opacol, bufprov->L[y][x], bufcolreserv->L[y][x]);
                                        bufcolfin->a[y][x] = intp(lp.opacol, bufprov->a[y][x], bufcolreserv->a[y][x]);
                                        bufcolfin->b[y][x] = intp(lp.opacol, bufprov->b[y][x], bufcolreserv->b[y][x]);
                                    }
                                }
                            }

                            if (conthr > 0.f && lp.mergemet != 4) {
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        bufcolfin->L[y][x] = intp(blend[y][x], bufcolfin->L[y][x], bufreser->L[y][x]);
                                        bufcolfin->a[y][x] = intp(blend[y][x], bufcolfin->a[y][x], bufreser->a[y][x]);
                                        bufcolfin->b[y][x] = intp(blend[y][x], bufcolfin->b[y][x], bufreser->b[y][x]);
                                    }
                                }
                            }
                        }

                        if (lp.mergecolMethod > 16) { //hue sat chroma luma
                            bufprov.reset(new LabImage(bfw, bfh));

                            if (lp.mergemet == 4) {
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        rdE[y][x] *= SQR(rdE[y][x]);
                                        bufprov->L[y][x] = intp(rdE[y][x], bufcolreserv->L[y][x], bufcolfin->L[y][x]);
                                        bufprov->a[y][x] = intp(rdE[y][x], bufcolreserv->a[y][x], bufcolfin->a[y][x]);
                                        bufprov->b[y][x] = intp(rdE[y][x], bufcolreserv->b[y][x], bufcolfin->b[y][x]);
                                    }
                                }
                            } else {
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        bufprov->L[y][x] = intp(rdE[y][x], bufcolfin->L[y][x], bufcolreserv->L[y][x]);
                                        bufprov->a[y][x] = intp(rdE[y][x], bufcolfin->a[y][x], bufcolreserv->a[y][x]);
                                        bufprov->b[y][x] = intp(rdE[y][x], bufcolfin->b[y][x], bufcolreserv->b[y][x]);
                                    }
                                }
                            }

#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfh ; y++) {
                                for (int x = 0; x < bfw; x++) {
                                    if (lp.mergecolMethod == 17) {
                                        const float huefin = xatan2f(bufprov->b[y][x], bufprov->a[y][x]);
                                        const float2 sincosval1 = xsincosf(huefin);
                                        const float chrores = std::sqrt(SQR(bufcolreserv->a[y][x]) + SQR(bufcolreserv->b[y][x]));
                                        buftemp->a[y][x] = chrores * sincosval1.y;
                                        buftemp->b[y][x] = chrores * sincosval1.x;
                                        buftemp->L[y][x] = bufcolreserv->L[y][x];
                                    } else if (lp.mergecolMethod == 18) {
                                        const float hueres = xatan2f(bufcolreserv->b[y][x], bufcolreserv->a[y][x]);
                                        const float2 sincosval2 = xsincosf(hueres);
                                        const float chrofin = std::sqrt(SQR(bufprov->a[y][x]) + SQR(bufprov->b[y][x]));
                                        buftemp->a[y][x] = chrofin * sincosval2.y;
                                        buftemp->b[y][x] = chrofin * sincosval2.x;
                                        buftemp->L[y][x] = bufcolreserv->L[y][x];
                                    } else if (lp.mergecolMethod == 19) {
                                        const float huefin = xatan2f(bufprov->b[y][x], bufprov->a[y][x]);
                                        const float2 sincosval3 = xsincosf(huefin);
                                        const float chrofin = std::sqrt(SQR(bufprov->a[y][x]) + SQR(bufprov->b[y][x]));
                                        buftemp->a[y][x] = chrofin * sincosval3.y;
                                        buftemp->b[y][x] = chrofin * sincosval3.x;
                                        buftemp->L[y][x] = bufcolreserv->L[y][x];
                                    } else if (lp.mergecolMethod == 20) {
                                        const float hueres = xatan2f(bufcolreserv->b[y][x], bufcolreserv->a[y][x]);
                                        const float2 sincosval4 = xsincosf(hueres);
                                        const float chrores = std::sqrt(SQR(bufcolreserv->a[y][x]) + SQR(bufcolreserv->b[y][x]));
                                        buftemp->a[y][x] = chrores * sincosval4.y;
                                        buftemp->b[y][x] = chrores * sincosval4.x;
                                        buftemp->L[y][x] = bufprov->L[y][x];
                                    }

                                    if (lp.mergemet == 4) {
                                        bufcolfin->L[y][x] = intp(lp.opacol, bufprov->L[y][x], bufcolfin->L[y][x]);
                                        bufcolfin->a[y][x] = intp(lp.opacol, bufprov->a[y][x], bufcolfin->a[y][x]);
                                        bufcolfin->b[y][x] = intp(lp.opacol, bufprov->b[y][x], bufcolfin->b[y][x]);
                                    } else {
                                        bufcolfin->L[y][x] = intp(lp.opacol, bufprov->L[y][x], bufcolreserv->L[y][x]);
                                        bufcolfin->a[y][x] = intp(lp.opacol, bufprov->a[y][x], bufcolreserv->a[y][x]);
                                        bufcolfin->b[y][x] = intp(lp.opacol, bufprov->b[y][x], bufcolreserv->b[y][x]);
                                    }
                                }
                            }

                            if (conthr > 0.f && lp.mergemet != 4) {
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        bufcolfin->L[y][x] = intp(blend[y][x], bufcolfin->L[y][x], bufcolreserv->L[y][x]);
                                        bufcolfin->a[y][x] = intp(blend[y][x], bufcolfin->a[y][x], bufcolreserv->a[y][x]);
                                        bufcolfin->b[y][x] = intp(blend[y][x], bufcolfin->b[y][x], bufcolreserv->b[y][x]);
                                    }
                                }
                            }
                        }


                        if (lp.mergecolMethod > 0 && lp.mergecolMethod <= 16) {
                            //first deltaE
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfh ; y++) {
                                for (int x = 0; x < bfw; x++) {
                                    bufcolfin->L[y][x] = intp(rdE[y][x], bufcolfin->L[y][x], bufcolreserv->L[y][x]);
                                    bufcolfin->a[y][x] = intp(rdE[y][x], bufcolfin->a[y][x], bufcolreserv->a[y][x]);
                                    bufcolfin->b[y][x] = intp(rdE[y][x], bufcolfin->b[y][x], bufcolreserv->b[y][x]);
                                }
                            }

                            //prepare RGB values in 0 1(or more)for current image and reserved
                            std::unique_ptr<Imagefloat> tmpImageorig(new Imagefloat(bfw, bfh));
                            lab2rgb(*bufcolfin, *tmpImageorig, params->icm.workingProfile);
                            tmpImageorig->normalizeFloatTo1();

                            std::unique_ptr<Imagefloat> tmpImagereserv(new Imagefloat(bfw, bfh));
                            lab2rgb(*bufcolreserv, *tmpImagereserv, params->icm.workingProfile);
                            tmpImagereserv->normalizeFloatTo1();

                            float minR = tmpImagereserv->r(0, 0);
                            float maxR = minR;
                            float minG = tmpImagereserv->g(0, 0);
                            float maxG = minG;
                            float minB = tmpImagereserv->b(0, 0);
                            float maxB = minB;

                            if (lp.mergecolMethod == 6 || lp.mergecolMethod == 9 || lp.mergecolMethod == 10 || lp.mergecolMethod == 11) {
#ifdef _OPENMP
                                #pragma omp parallel for reduction(max:maxR,maxG,maxB) reduction(min:minR,minG,minB) schedule(dynamic,16) if (multiThread)
#endif

                                for (int ir = 0; ir < bfh; ir++) {
                                    for (int jr = 0; jr < bfw; jr++) {
                                        minR = rtengine::min(minR, tmpImagereserv->r(ir, jr));
                                        maxR = rtengine::max(maxR, tmpImagereserv->r(ir, jr));
                                        minG = rtengine::min(minG, tmpImagereserv->g(ir, jr));
                                        maxG = rtengine::max(maxG, tmpImagereserv->g(ir, jr));
                                        minB = rtengine::min(minB, tmpImagereserv->b(ir, jr));
                                        maxB = rtengine::max(maxB, tmpImagereserv->b(ir, jr));
                                    }
                                }
                            }

                            //various combinations  subtract, multiply, difference, etc
                            if (lp.mergecolMethod == 1) { //subtract
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {//LIM(x 0 2) 2 arbitrary value but limit...
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, tmpImageorig->r(y, x) - tmpImagereserv->r(y, x), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, tmpImageorig->g(y, x) - tmpImagereserv->g(y, x), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, tmpImageorig->b(y, x) - tmpImagereserv->b(y, x), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 2) { //difference
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, std::fabs(tmpImageorig->r(y, x) - tmpImagereserv->r(y, x)), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, std::fabs(tmpImageorig->g(y, x) - tmpImagereserv->g(y, x)), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, std::fabs(tmpImageorig->b(y, x) - tmpImagereserv->b(y, x)), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 3) { //multiply
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, tmpImageorig->r(y, x) * tmpImagereserv->r(y, x), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, tmpImageorig->g(y, x) * tmpImagereserv->g(y, x), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, tmpImageorig->b(y, x) * tmpImagereserv->b(y, x), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 4) { //addition
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, tmpImageorig->r(y, x) + tmpImagereserv->r(y, x), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, tmpImageorig->g(y, x) + tmpImagereserv->g(y, x), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, tmpImageorig->b(y, x) + tmpImagereserv->b(y, x), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 5) { //divide
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, tmpImageorig->r(y, x) / (tmpImagereserv->r(y, x) + 0.00001f), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, tmpImageorig->g(y, x) / (tmpImagereserv->g(y, x) + 0.00001f), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, tmpImageorig->b(y, x) / (tmpImagereserv->b(y, x) + 0.00001f), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 6) { //soft light as Photoshop
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, softlig(tmpImageorig->r(y, x), tmpImagereserv->r(y, x), minR, maxR), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, softlig(tmpImageorig->g(y, x), tmpImagereserv->g(y, x), minG, maxG), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, softlig(tmpImageorig->b(y, x), tmpImagereserv->b(y, x), minB, maxB), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 7) { //soft light as illusions.hu
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, softlig2(LIM01(tmpImageorig->r(y, x)), LIM01(tmpImageorig->r(y, x))), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, softlig2(LIM01(tmpImageorig->g(y, x)), LIM01(tmpImageorig->g(y, x))), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, softlig2(LIM01(tmpImageorig->b(y, x)), LIM01(tmpImageorig->b(y, x))), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 8) { //soft light as W3C
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, softlig3(LIM01(tmpImageorig->r(y, x)), tmpImagereserv->r(y, x)), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, softlig3(LIM01(tmpImageorig->g(y, x)), tmpImagereserv->g(y, x)), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, softlig3(LIM01(tmpImageorig->b(y, x)), tmpImagereserv->b(y, x)), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 9) { //hard light overlay (float &b, float &a)
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, overlay(tmpImagereserv->r(y, x), tmpImageorig->r(y, x), minR, maxR), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, overlay(tmpImagereserv->g(y, x), tmpImageorig->g(y, x), minG, maxG), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, overlay(tmpImagereserv->b(y, x), tmpImageorig->b(y, x), minB, maxB), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 10) { //overlay overlay(float &a, float &b)
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, overlay(tmpImageorig->r(y, x), tmpImagereserv->r(y, x), minR, maxR), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, overlay(tmpImageorig->g(y, x), tmpImagereserv->g(y, x), minG, maxG), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, overlay(tmpImageorig->b(y, x), tmpImagereserv->b(y, x), minB, maxB), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 11) { //screen screen (float &a, float &b)
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, screen(tmpImageorig->r(y, x), tmpImagereserv->r(y, x), 1.f), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, screen(tmpImageorig->g(y, x), tmpImagereserv->g(y, x), 1.f), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, screen(tmpImageorig->b(y, x), tmpImagereserv->b(y, x), 1.f), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 12) { //darken only
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, rtengine::min(tmpImageorig->r(y, x), tmpImagereserv->r(y, x)), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, rtengine::min(tmpImageorig->g(y, x), tmpImagereserv->g(y, x)), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, rtengine::min(tmpImageorig->b(y, x), tmpImagereserv->b(y, x)), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 13) { //lighten only
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, rtengine::max(tmpImageorig->r(y, x), tmpImagereserv->r(y, x)), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, rtengine::max(tmpImageorig->g(y, x), tmpImagereserv->g(y, x)), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, rtengine::max(tmpImageorig->b(y, x), tmpImagereserv->b(y, x)), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 14) { //exclusion exclusion (float &a, float &b)
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, exclusion(tmpImageorig->r(y, x), tmpImagereserv->r(y, x)), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, exclusion(tmpImageorig->g(y, x), tmpImagereserv->g(y, x)), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, exclusion(tmpImageorig->b(y, x), tmpImagereserv->b(y, x)), tmpImageorig->b(y, x));
                                    }
                                }

                            } else if (lp.mergecolMethod == 15) { //Color burn
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, colburn(LIM01(tmpImageorig->r(y, x)), LIM01(tmpImagereserv->r(y, x))), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, colburn(LIM01(tmpImageorig->g(y, x)), LIM01(tmpImagereserv->g(y, x))), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, colburn(LIM01(tmpImageorig->b(y, x)), LIM01(tmpImagereserv->b(y, x))), tmpImageorig->b(y, x));
                                    }
                                }
                            } else if (lp.mergecolMethod == 16) { //Color dodge
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        tmpImageorig->r(y, x) = intp(lp.opacol, coldodge(LIM01(tmpImageorig->r(y, x)), LIM01(tmpImagereserv->r(y, x))), tmpImageorig->r(y, x));
                                        tmpImageorig->g(y, x) = intp(lp.opacol, coldodge(LIM01(tmpImageorig->g(y, x)), LIM01(tmpImagereserv->g(y, x))), tmpImageorig->g(y, x));
                                        tmpImageorig->b(y, x) = intp(lp.opacol, coldodge(LIM01(tmpImageorig->b(y, x)), LIM01(tmpImagereserv->b(y, x))), tmpImageorig->b(y, x));
                                    }
                                }
                            }

                            tmpImageorig->normalizeFloatTo65535();
                            rgb2lab(*tmpImageorig, *bufcolfin, params->icm.workingProfile);

                            if (conthr > 0.f && lp.mergemet != 4) {
#ifdef _OPENMP
                                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                                for (int y = 0; y < bfh ; y++) {
                                    for (int x = 0; x < bfw; x++) {
                                        bufcolfin->L[y][x] = intp(blend[y][x], bufcolfin->L[y][x], bufcolreserv->L[y][x]);
                                        bufcolfin->a[y][x] = intp(blend[y][x], bufcolfin->a[y][x], bufcolreserv->a[y][x]);
                                        bufcolfin->b[y][x] = intp(blend[y][x], bufcolfin->b[y][x], bufcolreserv->b[y][x]);
                                    }
                                }
                            }
                        }

                        if (lp.softradiuscol > 0.f) {
                            softproc(bufcolreserv.get(), bufcolfin.get(), lp.softradiuscol, bfh, bfw, 0.001, 0.00001, 0.5f, sk, multiThread, 1);
                        }

                        float meansob = 0.f;
                        const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparcol;
                        int bw = bufcolreserv->W;
                        int bh = bufcolreserv->H;

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                        for (int x = 0; x < bh; x++) {
                            for (int y = 0; y < bw; y++) {
                                bufcolfin->L[x][y] = intp(repart, bufcolreserv->L[x][y], bufcolfin->L[x][y]);
                                bufcolfin->a[x][y] = intp(repart, bufcolreserv->a[x][y], bufcolfin->a[x][y]);
                                bufcolfin->b[x][y] = intp(repart, bufcolreserv->b[x][y], bufcolfin->b[x][y]);
                            }
                        }

                        transit_shapedetect2(sp, 0.f, 0.f, call, 0, bufcolreserv.get(), bufcolfin.get(), originalmaskcol.get(), hueref, chromaref, lumaref, sobelref, meansob, blend2, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                    }

                    if (!nottransit) {
//gradient
                        if (lp.strcol != 0.f) {
                            struct grad_params gp;
                            calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH,  3, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    bufcolfin->L[ir][jr] *= corrFactor;
                                }
                        }

                        if (lp.strcolab != 0.f) {
                            struct grad_params gpab;
                            calclocalGradientParams(call, lp, gpab, ystart, xstart, yend, xend, bfw, bfh, oW, oH, tX, tY, tW, tH, 5, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gpab, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    bufcolfin->a[ir][jr] *= corrFactor;
                                    bufcolfin->b[ir][jr] *= corrFactor;
                                }
                        }

                        if (lp.strcolh != 0.f) {
                            struct grad_params gph;
                            calclocalGradientParams(call, lp, gph, ystart, xstart, yend, xend,  bfw, bfh, oW, oH, tX, tY, tW, tH, 6, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int ir = 0; ir < bfh; ir++)
                                for (int jr = 0; jr < bfw; jr++) {
                                    const float corrFactor = ImProcFunctions::calcGradientFactor(gph, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                                    const float aa = bufcolfin->a[ir][jr];
                                    const float bb = bufcolfin->b[ir][jr];
                                    const float chrm = std::sqrt(SQR(aa) + SQR(bb));
                                    const float HH = xatan2f(bb, aa);

                                    float cor = 0.f;

                                    if (corrFactor < 1.f) {
                                        cor = - 2.5f * (1.f - corrFactor);
                                    } else if (corrFactor > 1.f) {
                                        cor = 0.03f * (corrFactor - 1.f);
                                    }

                                    float newhr = HH + cor;

                                    if (newhr > rtengine::RT_PI_F) {
                                        newhr -= 2 * rtengine::RT_PI_F;
                                    } else if (newhr < -rtengine::RT_PI_F) {
                                        newhr += 2 * rtengine::RT_PI_F;
                                    }

                                    const float2 sincosval = xsincosf(newhr);
                                    bufcolfin->a[ir][jr] = clipC(chrm * sincosval.y);
                                    bufcolfin->b[ir][jr] = clipC(chrm * sincosval.x);
                                }
                        }



                        /*
                                                float gamma = lp.gamc;
                                                rtengine::GammaValues g_a; //gamma parameters
                                                double pwr = 1.0 / (double) lp.gamc;//default 3.0 - gamma Lab
                                                double ts = 9.03296;//always the same 'slope' in the extreme shadows - slope Lab
                                                rtengine::Color::calcGamma(pwr, ts, g_a); // call to calcGamma with selected gamma and slope
                        */
                        if (gamma1 != 1.f) {
#ifdef _OPENMP
                            #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                            for (int y = 0; y < bfh; ++y) {//apply inverse gamma 3.f and put result in range 32768.f
                                int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                                for (; x < bfw - 3; x += 4) {
                                    STVFU(bufcolfin->L[y][x], F2V(32768.f) * gammalog(LVFU(bufcolfin->L[y][x]) / F2V(32768.f), F2V(gamma1), F2V(ts1), F2V(g_a[3]), F2V(g_a[4])));
                                }

#endif

                                for (; x < bfw; ++x) {
                                    bufcolfin->L[y][x] = 32768.f * gammalog(bufcolfin->L[y][x] / 32768.f, gamma1, ts1, g_a[3], g_a[4]);
                                }
                            }
                        }


                        if (lp.softradiuscol > 0.f) {
                            softproc(bufcolorig.get(), bufcolfin.get(), lp.softradiuscol, bfh, bfw, 0.001, 0.00001, 0.5f, sk, multiThread, 1);
                        }

                        //mask recovery

                        if (lp.enaColorMask && lp.recothrc != 1.f) {
                            float recoth = lp.recothrc;

                            if (lp.recothrc < 1.f) {
                                recoth = -1.f * recoth + 2.f;
                            }

                            float hig = lp.higthrc;
                            float low = lp.lowthrc;
                            //  float recoth = lp.recothrc;
                            float decay = lp.decayc;
                            bool invmask = false;
                            maskrecov(bufcolfin.get(), original, bufmaskblurcol.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
                        }

                        const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparcol;
                        int bw = bufcolorig->W;
                        int bh = bufcolorig->H;

#ifdef _OPENMP
                        #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

                        for (int x = 0; x < bh; x++) {
                            for (int y = 0; y < bw; y++) {
                                bufcolfin->L[x][y] = intp(repart, bufcolorig->L[x][y], bufcolfin->L[x][y]);
                                bufcolfin->a[x][y] = intp(repart, bufcolorig->a[x][y], bufcolfin->a[x][y]);
                                bufcolfin->b[x][y] = intp(repart, bufcolorig->b[x][y], bufcolfin->b[x][y]);
                            }
                        }

                        float meansob = 0.f;

                        if (lp.recothrc >= 1.f) {
                            transit_shapedetect2(sp, 0.f, 0.f, call, 0, bufcolorig.get(), bufcolfin.get(), originalmaskcol.get(), hueref, chromaref, lumaref, sobelref, meansob, blend2, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                        } else {
                            transit_shapedetect2(sp, 0.f, 0.f, call, 0, bufcolorig.get(), bufcolfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, meansob, blend2, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                        }
                    }

                }

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }

            }
        }
    }

//inverse
    else if (lp.inv && (lp.chro != 0 || lp.ligh != 0 || exlocalcurve || lp.showmaskcolmetinv == 0 || lp.enaColorMaskinv) && lp.colorena) {
        float adjustr = 1.0f;

//adapt chroma to working profile
        if (params->icm.workingProfile == "ProPhoto")   {
            adjustr = 1.2f;   // 1.2 instead 1.0 because it's very rare to have C>170..
        } else if (params->icm.workingProfile == "Adobe RGB")  {
            adjustr = 1.8f;
        } else if (params->icm.workingProfile == "sRGB")       {
            adjustr = 2.0f;
        } else if (params->icm.workingProfile == "WideGamut")  {
            adjustr = 1.2f;
        } else if (params->icm.workingProfile == "Beta RGB")   {
            adjustr = 1.4f;
        } else if (params->icm.workingProfile == "BestRGB")    {
            adjustr = 1.4f;
        } else if (params->icm.workingProfile == "BruceRGB")   {
            adjustr = 1.8f;
        }

        std::unique_ptr<LabImage> bufmaskblurcol;
        std::unique_ptr<LabImage> originalmaskcol;
        const std::unique_ptr<LabImage> bufcolorig(new LabImage(TW, TH));

        if (lp.enaColorMaskinv || lp.showmaskcolmetinv == 1) {
            bufmaskblurcol.reset(new LabImage(TW, TH, true));
            originalmaskcol.reset(new LabImage(TW, TH));
        }

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

        for (int y = 0; y < TH ; y++) {
            for (int x = 0; x < TW; x++) {
                bufcolorig->L[y][x] = original->L[y][x];
            }
        }

        constexpr int inv = 1;
        const bool showmaske = lp.showmaskcolmetinv == 1;
        const bool enaMask = lp.enaColorMaskinv;
        constexpr bool deltaE = false;
        constexpr bool modmask = false;
        const bool zero = lp.showmaskcolmetinv == 0;
        constexpr bool modif = false;

        const float chrom = lp.chromacol;
        const float rad = lp.radmacol;
        const float gamma = lp.gammacol;
        const float slope = lp.slomacol;
        const float blendm = lp.blendmacol;
        const float lap = params->locallab.spots.at(sp).lapmaskcol;
        const bool pde = params->locallab.spots.at(sp).laplac;
        int shado = params->locallab.spots.at(sp).shadmaskcol;
        int level_bl = params->locallab.spots.at(sp).csthresholdcol.getBottomLeft();
        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();
        int sco = params->locallab.spots.at(sp).scopemask;
        int shortcu = lp.mergemet; //params->locallab.spots.at(sp).shortc;
        int lumask = params->locallab.spots.at(sp).lumask;
        float strumask = 0.02 * params->locallab.spots.at(sp).strumaskcol;

        const float mindE = 2.f + MINSCOPE * sco * lp.thr;
        const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
        const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
        const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
        constexpr float amountcd = 0.f;
        constexpr float anchorcd = 50.f;
        const int highl = 0;
        maskcalccol(call, false, pde, TW, TH, oW, oH, tX, tY, tW, tH, 0, 0, 0, 0, sk, cx, cy, bufcolorig.get(), bufmaskblurcol.get(), originalmaskcol.get(), original, reserved, inv, lp,
                    strumask, params->locallab.spots.at(sp).toolcol,
                    locccmasCurve, lcmasutili, locllmasCurve, llmasutili, lochhmasCurve, lhmasutili, llochhhmasCurve, lhhmasutili, multiThread,
                    enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmasklocalcurve, localmaskutili, loclmasCurvecolwav, lmasutilicolwav,
                    level_bl, level_hl, level_br, level_hr,
                    shortcu, false, hueref, chromaref, lumaref,
                    maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, lp.fftColorMask, lp.blurcolmask, lp.contcolmask, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                   );

        if (lp.showmaskcolmetinv == 1) {
            showmask(lumask, lp, 0, 0, cx, cy, TW, TH, bufcolorig.get(), transformed, bufmaskblurcol.get(), inv);
            return;
        }

        if (lp.showmaskcolmetinv == 0 || lp.enaColorMaskinv) {
            InverseColorLight_Local(false, false, sp, 0, lp, originalmaskcol.get(), lightCurveloc, hltonecurveloc, shtonecurveloc, tonecurveloc, exlocalcurve, cclocalcurve, adjustr, localcutili, lllocalcurve, locallutili, original, transformed, cx, cy, hueref, chromaref, lumaref, sk);

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }

//begin common mask
    if (lp.maskena) {
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;

        if (bfw >= mSP && bfh >= mSP) {

            if (lp.blurma >= 0.25f && lp.fftma && call == 2) {
                optfft(N_fftwsize, bfh, bfw, bfh, bfw, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            array2D<float> blechro(bfw, bfh);
            array2D<float> ble(bfw, bfh);
            array2D<float> hue(bfw, bfh);
            array2D<float> guid(bfw, bfh);

            std::unique_ptr<LabImage> bufcolorigsav;
            std::unique_ptr<LabImage> bufcolorig;
            std::unique_ptr<LabImage> bufcolfin;
            std::unique_ptr<LabImage> bufmaskblurcol;
            std::unique_ptr<LabImage> originalmaskcol;
            std::unique_ptr<LabImage> bufcolreserv;

            int wo = original->W;
            int ho = original->H;
            LabImage *origsav = nullptr;
            origsav = new LabImage(wo, ho);
            origsav->CopyFrom(original);

            if (call <= 3) {
                bufcolorig.reset(new LabImage(bfw, bfh));
                bufcolfin.reset(new LabImage(bfw, bfh));
                bufcolorigsav.reset(new LabImage(bfw, bfh));

                if (lp.showmask_met == 1  || lp.ena_Mask || lp.showmask_met == 2 || lp.showmask_met == 3) {
                    bufmaskblurcol.reset(new LabImage(bfw, bfh, true));
                    originalmaskcol.reset(new LabImage(bfw, bfh));
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufcolorig->L[y][x] = original->L[y + ystart][x + xstart];
                        bufcolorig->a[y][x] = original->a[y + ystart][x + xstart];
                        bufcolorig->b[y][x] = original->b[y + ystart][x + xstart];

                        bufcolorigsav->L[y][x] = original->L[y + ystart][x + xstart];
                        bufcolorigsav->a[y][x] = original->a[y + ystart][x + xstart];
                        bufcolorigsav->b[y][x] = original->b[y + ystart][x + xstart];

                        bufcolfin->L[y][x] = original->L[y + ystart][x + xstart];
                        bufcolfin->a[y][x] = original->a[y + ystart][x + xstart];
                        bufcolfin->b[y][x] = original->b[y + ystart][x + xstart];
                    }
                }

                const int inv = 0;
                const bool showmaske = lp.showmask_met == 2;
                const bool enaMask = lp.ena_Mask;
                const bool deltaE = lp.showmask_met == 3;
                const bool modmask = lp.showmask_met == 1;
                const bool zero = lp.showmask_met == 0;
                const bool modif = lp.showmask_met == 1;
                const float chrom = params->locallab.spots.at(sp).chromask;
                const float rad = params->locallab.spots.at(sp).radmask;
                const float gamma = params->locallab.spots.at(sp).gammask;
                const float slope =  params->locallab.spots.at(sp).slopmask;
                float blendm =  0.1 * params->locallab.spots.at(sp).blendmask;
                float blendmab =  params->locallab.spots.at(sp).blendmaskab;

                if (lp.showmask_met == 2) {
                    blendm = 0.f;//normalize behavior mask with others no action of blend
                    blendmab = 0.f;
                }

                const float lap = params->locallab.spots.at(sp).lapmask;
                const bool pde = params->locallab.spots.at(sp).laplac;
                const int shado = params->locallab.spots.at(sp).shadmask;
                const int sco = params->locallab.spots.at(sp).scopemask;
                const int level_bl = params->locallab.spots.at(sp).csthresholdmask.getBottomLeft();
                const int level_hl = params->locallab.spots.at(sp).csthresholdmask.getTopLeft();
                const int level_br = params->locallab.spots.at(sp).csthresholdmask.getBottomRight();
                const int level_hr = params->locallab.spots.at(sp).csthresholdmask.getTopRight();
                const int shortcu = lp.mergemet; //params->locallab.spots.at(sp).shortc;
                const int lumask = params->locallab.spots.at(sp).lumask;
                const float strumask = 0.02 * params->locallab.spots.at(sp).strumaskmask;
                const float softr = params->locallab.spots.at(sp).softradiusmask;

                const float mindE = 2.f + MINSCOPE * sco * lp.thr;
                const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
                const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
                const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
                const float amountcd = 0.f;
                const float anchorcd = 50.f;
                const int highl = 0;
                bool astool = params->locallab.spots.at(sp).toolmask;
                maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufcolorig.get(), bufmaskblurcol.get(), originalmaskcol.get(), original, reserved, inv, lp,
                            strumask, astool,
                            locccmas_Curve, lcmas_utili, locllmas_Curve, llmas_utili, lochhmas_Curve, lhmas_utili, lochhhmas_Curve, lhhmas_utili, multiThread,
                            enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendmab, shado, highl, amountcd, anchorcd, lmasklocal_curve, localmask_utili, loclmasCurve_wav, lmasutili_wav,
                            level_bl, level_hl, level_br, level_hr,
                            shortcu, delt, hueref, chromaref, lumaref,
                            maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, lp.fftma, lp.blurma, lp.contma, 12, fab, fw, fh, ksk, kskx, kbh, kbw
                           );


                if (lp.showmask_met == 2) {
                    showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufcolorig.get(), transformed, bufmaskblurcol.get(), 0);
                    return;
                }

#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufcolfin->L[y][x] = bufcolorig->L[y][x];
                        bufcolfin->a[y][x] = bufcolorig->a[y][x];
                        bufcolfin->b[y][x] = bufcolorig->b[y][x];
                        hue[y][x] = xatan2f(bufcolfin->b[y][x], bufcolfin->a[y][x]);
                        const float chromah = std::sqrt(SQR(bufcolfin->b[y][x]) + SQR(bufcolfin->a[y][x]));
                        ble[y][x] = bufcolfin->L[y][x] / 32768.f;
                        blechro[y][x] = chromah / 32768.f;
                        guid[y][x] = bufcolorigsav->L[y][x] / 32768.f;
                    }
                }

                if (softr != 0.f) {//soft for L a b because we change color...
                    const float tmpblur = softr < 0.f ? -1.f / softr : 1.f + softr;
                    const int r1 = rtengine::max<int>(4 / sk * tmpblur + 0.5f, 1);
                    const int r2 = rtengine::max<int>(25 / sk * tmpblur + 0.5f, 1);

                    constexpr float epsilmax = 0.005f;
                    constexpr float epsilmin = 0.00001f;

                    constexpr float aepsil = (epsilmax - epsilmin) / 100.f;
                    constexpr float bepsil = epsilmin;
                    const float epsil = softr < 0.f ? 0.001f : aepsil * softr + bepsil;

                    rtengine::guidedFilter(guid, blechro, blechro, r1, epsil, multiThread);
                    rtengine::guidedFilter(guid, ble, ble, r2, 0.2f * epsil, multiThread);

#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int y = 0; y < bfh; y++) {
                        for (int x = 0; x < bfw; x++) {
                            float2 sincosval = xsincosf(hue[y][x]);
                            bufcolfin->L[y][x] = 32768.f * ble[y][x];
                            bufcolfin->a[y][x] = 32768.f * blechro[y][x] * sincosval.y;
                            bufcolfin->b[y][x] = 32768.f * blechro[y][x] * sincosval.x;
                        }
                    }
                }



                float meansob = 0.f;
                transit_shapedetect2(sp, 0.f, 0.f, call, 20, bufcolorigsav.get(), bufcolfin.get(), originalmaskcol.get(), hueref, chromaref, lumaref, sobelref, meansob, nullptr, lp, origsav, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
                delete origsav;
                origsav    = NULL;

                if (lp.recur) {
                    original->CopyFrom(transformed, multiThread);
                    float avge;
                    calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
                }

            }
        }
    }

//end common mask
    //Cie 
    rdx = params->locallab.spots.at(sp).redxl;
    rdy = params->locallab.spots.at(sp).redyl;
    grx = params->locallab.spots.at(sp).grexl;
    gry = params->locallab.spots.at(sp).greyl;
    blx = params->locallab.spots.at(sp).bluxl;
    bly = params->locallab.spots.at(sp).bluyl;

    if (params->locallab.spots.at(sp).expcie   && params->locallab.spots.at(sp).modecie == "com"  && lp.activspot) { //ciecam
        int ystart = rtengine::max(static_cast<int>(lp.yc - lp.lyT) - cy, 0);
        int yend = rtengine::min(static_cast<int>(lp.yc + lp.ly) - cy, original->H);
        int xstart = rtengine::max(static_cast<int>(lp.xc - lp.lxL) - cx, 0);
        int xend = rtengine::min(static_cast<int>(lp.xc + lp.lx) - cx, original->W);
        int bfh = yend - ystart;
        int bfw = xend - xstart;

        if (bfh >= mSP && bfw >= mSP) {

            if (lp.blurciemask >= 0.25f && lp.fftcieMask && call == 2) {
                optfft(N_fftwsize, bfh, bfw, bfh, bfw, lp, original->H, original->W, xstart, ystart, xend, yend, cx, cy, lp.fullim);
            }

            const std::unique_ptr<LabImage> bufexporig(new LabImage(bfw, bfh)); //buffer for data in zone limit
            const std::unique_ptr<LabImage> bufexpfin(new LabImage(bfw, bfh)); //buffer for data in zone limit
            std::unique_ptr<LabImage> bufmaskorigcie;
            std::unique_ptr<LabImage> bufmaskblurcie;
            std::unique_ptr<LabImage> originalmaskcie;

            if (lp.showmaskciemet == 2  || lp.enacieMask || lp.showmaskciemet == 3 || lp.showmaskciemet == 4) {
                bufmaskorigcie.reset(new LabImage(bfw, bfh));
                bufmaskblurcie.reset(new LabImage(bfw, bfh));
                originalmaskcie.reset(new LabImage(bfw, bfh));
            }



#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

            for (int y = 0; y < bfh; y++) {
                for (int x = 0; x < bfw; x++) {
                    bufexporig->L[y][x] = original->L[y + ystart][x + xstart];
                    bufexporig->a[y][x] = original->a[y + ystart][x + xstart];
                    bufexporig->b[y][x] = original->b[y + ystart][x + xstart];
                }
            }

            bool HHcurvejz = false, CHcurvejz = false, LHcurvejz = false;

            if (params->locallab.spots.at(sp).expcie  && params->locallab.spots.at(sp).modecam == "jz") {//some cam16 elements for Jz
                ImProcFunctions::ciecamloc_02float(lp, sp, bufexporig.get(), bfw, bfh, 10, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
            }

            if (lochhCurvejz && HHutilijz) {
                for (int i = 0; i < 500; i++) {
                    if (lochhCurvejz[i] != 0.5f) {
                        HHcurvejz = true;
                        break;
                    }
                }
            }

            if (locchCurvejz && CHutilijz) {
                for (int i = 0; i < 500; i++) {
                    if (locchCurvejz[i] != 0.5f) {
                        CHcurvejz = true;
                        break;
                    }
                }
            }

            if (loclhCurvejz && LHutilijz) {
                for (int i = 0; i < 500; i++) {
                    if (loclhCurvejz[i] != 0.5f) {
                        LHcurvejz = true;
                        break;
                    }
                }
            }

            int inv = 0;
            bool showmaske = false;
            bool enaMask = false;
            bool deltaE = false;
            bool modmask = false;
            bool zero = false;
            bool modif = false;

            if (lp.showmaskciemet == 3) {
                showmaske = true;
            }

            if (lp.enacieMask) {
                enaMask = true;
            }

            if (lp.showmaskciemet == 4) {
                deltaE = true;
            }

            if (lp.showmaskciemet == 2) {
                modmask = true;
            }

            if (lp.showmaskciemet == 1) {
                modif = true;
            }

            if (lp.showmaskciemet == 0) {
                zero = true;
            }

            float chrom = lp.chromacie;
            float rad = lp.radmacie;
            float gamma = params->locallab.spots.at(sp).gammaskcie;
            float slope = params->locallab.spots.at(sp).slomaskcie;
            float blendm = lp.blendmacie;
            float lap = params->locallab.spots.at(sp).lapmaskcie;
            bool pde =  params->locallab.spots.at(sp).laplac;
            LocwavCurve dummy;
            bool delt = params->locallab.spots.at(sp).deltae;
            int sco = params->locallab.spots.at(sp).scopemask;
            int shortcu = 0;//lp.mergemet; //params->locallab.spots.at(sp).shortc;
            const int level_bl = params->locallab.spots.at(sp).csthresholdcie.getBottomLeft();
            const int level_hl = params->locallab.spots.at(sp).csthresholdcie.getTopLeft();
            const int level_br = params->locallab.spots.at(sp).csthresholdcie.getBottomRight();
            const int level_hr = params->locallab.spots.at(sp).csthresholdcie.getTopRight();

            const float mindE = 2.f + MINSCOPE * sco * lp.thr;
            const float maxdE = 5.f + MAXSCOPE * sco * (1 + 0.1f * lp.thr);
            const float mindElim = 2.f + MINSCOPE * limscope * lp.thr;
            const float maxdElim = 5.f + MAXSCOPE * limscope * (1 + 0.1f * lp.thr);
            int lumask = params->locallab.spots.at(sp).lumask;
            float amountcd = 0.f;
            float anchorcd = 50.f;
            LocHHmaskCurve lochhhmasCurve;
            bool astool = params->locallab.spots.at(sp).toolcie;
            const float strumask = 0.02 * params->locallab.spots.at(sp).strumaskcie;
            const int shado = params->locallab.spots.at(sp).shadmaskcie;
            const int highl = params->locallab.spots.at(sp).highmaskcie;

            maskcalccol(call, false, pde, bfw, bfh, oW, oH, tX, tY, tW, tH, xstart, ystart, xend, yend, sk, cx, cy, bufexporig.get(), bufmaskorigcie.get(), originalmaskcie.get(), original, reserved, inv, lp,
                        strumask, astool,
                        locccmascieCurve, lcmascieutili, locllmascieCurve, llmascieutili, lochhmascieCurve, lhmascieutili, llochhhmascieCurve, lhhmascieutili, multiThread,
                        enaMask, showmaske, deltaE, modmask, zero, modif, chrom, rad, lap, gamma, slope, blendm, blendm, shado, highl, amountcd, anchorcd, lmaskcielocalcurve, localmaskcieutili, loclmasCurveciewav, lmasutiliciewav,
                        level_bl, level_hl, level_br, level_hr,
                        shortcu, delt, hueref, chromaref, lumaref,
                        maxdE, mindE, maxdElim, mindElim, lp.iterat, limscope, sco, lp.fftcieMask, lp.blurciemask, lp.contciemask, -1, fab, fw, fh, ksk, kskx, kbh, kbw
                       );

            if (lp.showmaskciemet == 3) {
                showmask(lumask, lp, xstart, ystart, cx, cy, bfw, bfh, bufexporig.get(), transformed, bufmaskorigcie.get(), 0);

                return;
            }



            if (lp.showmaskciemet == 0 || lp.showmaskciemet == 1  || lp.showmaskciemet == 2 || lp.showmaskciemet == 4 || lp.enacieMask) {
#ifdef _OPENMP
                #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif
                for (int y = 0; y < bfh ; y++) {
                    for (int x = 0; x < bfw; x++) {
                        bufexpfin->L[y][x] = original->L[y + ystart][x + xstart];
                        bufexpfin->a[y][x] = original->a[y + ystart][x + xstart];
                        bufexpfin->b[y][x] = original->b[y + ystart][x + xstart];
                    }
                }

                if (params->locallab.spots.at(sp).expprecam && params->locallab.spots.at(sp).modecam == "cam16") {
                    TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile);
                    TMatrix wiprof = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile);

                    float toxyz[3][3] = {
                        {
                        static_cast<float>(wprof[0][0] / static_cast<double>(Color::D50x)),
                        static_cast<float>(wprof[0][1] / static_cast<double>(Color::D50x)),
                        static_cast<float>(wprof[0][2] / static_cast<double>(Color::D50x))
                        }, {
                        static_cast<float>(wprof[1][0]),
                        static_cast<float>(wprof[1][1]),
                        static_cast<float>(wprof[1][2])
                        }, {
                        static_cast<float>(wprof[2][0] / static_cast<double>(Color::D50z)),
                        static_cast<float>(wprof[2][1] / static_cast<double>(Color::D50z)),
                        static_cast<float>(wprof[2][2] / static_cast<double>(Color::D50z))
                        }
                    };

                    float maxFactorToxyz = max(toxyz[1][0], toxyz[1][1], toxyz[1][2]);
                    float equalR = maxFactorToxyz / toxyz[1][0];
                    float equalG = maxFactorToxyz / toxyz[1][1];
                    float equalB = maxFactorToxyz / toxyz[1][2];
                    //inverse matrix user select
                    double wip[3][3] = {
                        {wiprof[0][0], wiprof[0][1], wiprof[0][2]},
                        {wiprof[1][0], wiprof[1][1], wiprof[1][2]},
                        {wiprof[2][0], wiprof[2][1], wiprof[2][2]}
                    };
                    double wp[3][3] = {
                        {wprof[0][0], wprof[0][1], wprof[0][2]},
                        {wprof[1][0], wprof[1][1], wprof[1][2]},
                        {wprof[2][0], wprof[2][1], wprof[2][2]}
                    };
                    Imagefloat *tmpImage = nullptr;
                    tmpImage = new Imagefloat(bfw, bfh);
                    Imagefloat *tmpImage2 = nullptr;
                    tmpImage2 = new Imagefloat(bfw, bfh);
                    Imagefloat *tmpImagelog = nullptr;
                    tmpImagelog = new Imagefloat(bfw, bfh);
                    
                    lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile);
                    Glib::ustring prof = params->icm.workingProfile;
                        
                        //LUT to inverse color
                    LUTf rCurve;
                    LUTf gCurve;
                    LUTf bCurve;
                    CurveFactory::RGBCurve(params->locallab.spots.at(sp).invcurve, rCurve, sk);//generated curve with inverse color
                    CurveFactory::RGBCurve(params->locallab.spots.at(sp).invcurve, gCurve, sk);
                    CurveFactory::RGBCurve(params->locallab.spots.at(sp).invcurve, bCurve, sk);

                    float gamtone = params->locallab.spots.at(sp).gamjcie;
                    float slotone = params->locallab.spots.at(sp).slopjcie;
                    cmsHTRANSFORM dummy = nullptr;
                    //int prim = 3;
                    int typ = 1;
                    rdx = params->locallab.spots.at(sp).redxl;
                    rdy = params->locallab.spots.at(sp).redyl;
                    grx = params->locallab.spots.at(sp).grexl;
                    gry = params->locallab.spots.at(sp).greyl;
                    blx = params->locallab.spots.at(sp).bluxl;
                    bly = params->locallab.spots.at(sp).bluyl; 
                    /*
                    if (params->locallab.spots.at(sp).complexcie == 2) {
                        printf("Mode complexity=%i Prov=%s\n", params->locallab.spots.at(sp).complexcie, prof.c_str());
                        printf("prim=%s \n", params->locallab.spots.at(sp).primMethod.c_str());
                    }
                    */
                    if (params->locallab.spots.at(sp).primMethod == "beta") {
                        typ = 5;
                        ill = 2;
                        prim = 11;
                    } else if (params->locallab.spots.at(sp).primMethod == "srgb") {
                        prim = 1;
                        ill = 5;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "ado") {
                        prim = 2;
                        ill = 5;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "pro") {
                        prim = 3;
                        ill = 2;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "rec") {
                        prim = 4;
                        ill = 5;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "ac1") {
                        prim = 5;
                        ill = 4;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "wid") {
                        prim = 6;
                        ill = 2;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "jdcmax") {
                        prim = 8;
                        ill = 2;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "jdcmaxstdA") {
                        prim = 9;
                        ill = 8;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "ac0") {
                        prim = 7;
                        ill = 4;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "best") {
                        prim = 12;
                        ill = 2;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "bru") {
                        prim = 10;
                        ill = 5;
                        typ = 5;
                    } else if (params->locallab.spots.at(sp).primMethod == "free") {
                        prim = 15;
                        typ = 5;
                        
                        rdx = params->locallab.spots.at(sp).redxl;
                        rdy = params->locallab.spots.at(sp).redyl;
                        grx = params->locallab.spots.at(sp).grexl;
                        gry = params->locallab.spots.at(sp).greyl;
                        blx = params->locallab.spots.at(sp).bluxl;
                        bly = params->locallab.spots.at(sp).bluyl; 
                        
                    }
                    int catx = 0; 
                     if (params->locallab.spots.at(sp).catMethod == "brad") {
                        catx = 0;
                    } else if (params->locallab.spots.at(sp).catMethod == "cat16") {
                        catx = 1;
                    } else if (params->locallab.spots.at(sp).catMethod == "cat02") {
                        catx = 2;
                    } else if (params->locallab.spots.at(sp).catMethod == "vky") {
                        catx = 3;
                    } else if (params->locallab.spots.at(sp).catMethod == "xyz") {
                        catx = 4;
                    }
                    tmpImage->copyData(tmpImage2);

                    params->locallab.spots.at(sp).catMethod;
                    int locprim = 1;
                    bool gamcie = params->locallab.spots.at(sp).gamutcie;
                    float rx, ry, gx, gy, bx, by = 0.f;
                    float mx, my, mxe, mye, mdat = 0.f;
                    
                    if(lp.midtcie != 0 && lp.midtmet == 0) {
                        ImProcFunctions::tone_eqcam(this, tmpImage, lp.midtcie, params->icm.workingProfile, sk, multiThread);
                    }
                    double p[6] = {0., 0., 0., 0., 0., 0.};

                    workingtrc(sp, tmpImage, tmpImage, bfw, bfh, -5, prof, 2.4, 12.92310, 0, ill, 0, 0, rx, ry, gx, gy, bx, by, mx, my, mxe, mye, mdat, p, dummy, true, false, false, false);
                    workingtrc(sp, tmpImage, tmpImage, bfw, bfh, typ, prof, gamtone, slotone, catx, ill, prim, locprim, rdx, rdy, grx, gry, blx, bly, meanx, meany, meanxe, meanye, maxdat, p, dummy, false, true, true, gamcie);//with gamut control
                    float satu = params->locallab.spots.at(sp).satjcie;
                    if (satu > 0.f) {//saturation
                        ImProcFunctions::apsatur(sp, tmpImage, tmpImage2, bfw, bfh, satu) ;
                    }
 
                    if(lp.midtcie != 0 && lp.midtmet == 1) {
                        ImProcFunctions::tone_eqcam(this, tmpImage, lp.midtcie, params->icm.workingProfile, sk, multiThread);
                    }
                    
                    tmpImage->copyData(tmpImagelog);

                    if(params->locallab.spots.at(sp).logcie & !params->locallab.spots.at(sp).logcieq ) {
                       log_encode(tmpImagelog, lp, multiThread, bfw, bfh);
                        float strlog = 0.01f * (float) params->locallab.spots.at(sp).strcielog;


#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) = intp(strlog, tmpImagelog->r(y, x), tmpImage->r(y, x));
                                 tmpImage->g(y, x) = intp(strlog, tmpImagelog->g(y, x), tmpImage->g(y, x));
                                 tmpImage->b(y, x) = intp(strlog, tmpImagelog->b(y, x), tmpImage->b(y, x));
                           
                            }
                        }
                    }
                    
                   float ksr = 1.f;
                   float ksb = 1.f;
                   float ksg = 1.f;

                   if(lp.smoothciem == 7) {//TRC mode
                                            
                        ksr = params->locallab.spots.at(sp).kslopesmor;
                        float gamr = 2.4f * ksr;
                        float slr = 12.92f;
                        if(!params->locallab.spots.at(sp).smoothcietrcrel) {//relative gamma
                            gamr = 2.4f * ksr;
                            slr = 12.92f;
                            if(gamr < 2.f) {
                                slr = 2.5f * gamr - 2.f;
                            } else if (gamr < 2.2f) {
                                slr = 7.5f * gamr - 12.f;
                            } else if (gamr < 2.4f) {
                                slr = 42.1f * gamr - 88.12f;
                            } else if (gamr < 2.6f) {
                                slr = 25.4f * gamr - 48.04f;
                            } else if (gamr < 2.88f) {
                                slr = 7.142857f * gamr - 0.571428f;
                            } else if (gamr < 3.6f) {
                                slr = 13.8888f * gamr - 20.f;
                            }
                        }
                        GammaValues g_ar; //gamma parameters
                        double pwrr = 1.0 / static_cast<double>(gamr);
                        Color::calcGamma(pwrr, slr, g_ar); // call to calcGamma with selected gamma and slope

                        ksg = params->locallab.spots.at(sp).kslopesmog;
                        float gamg = 2.4f * ksg;
                        float slg = 12.92f;
                        if(!params->locallab.spots.at(sp).smoothcietrcrel) {
                            gamg = 2.4f * ksg;
                            slg = 12.92f;
                            if(gamg < 2.f) {
                                slg = 2.5f * gamg - 2.f;
                            } else if (gamg < 2.2f) {
                                slg = 7.5f * gamg - 12.f;
                            } else if (gamr < 2.4f) {
                                slg = 42.1f * gamg - 88.12f;
                            } else if (gamg < 2.6f) {
                                slg = 25.4f * gamg - 48.04f;
                            } else if (gamg < 2.88f) {
                                slg = 7.142857f * gamg - 0.571428f;
                            } else if (gamg < 3.6f) {
                            slg = 13.8888f * gamg - 20.f;
                            }
                        }
                        GammaValues g_ag; //gamma parameters
                        double pwrg = 1.0 / static_cast<double>(gamg);
                        Color::calcGamma(pwrg, slg, g_ag); // call to calcGamma with selected gamma and slope

                        ksb = params->locallab.spots.at(sp).kslopesmob;
                        float gamb = 2.4f * ksb;
                        float slb = 12.92f;
                        if(!params->locallab.spots.at(sp).smoothcietrcrel) {

                            gamb = 2.4f * ksb;
                            slb = 12.92f;
                            if(gamb < 2.f) {
                                slb = 2.5f * gamb - 2.f;
                            } else if (gamb < 2.2f) {
                                slb = 7.5f * gamb - 12.f;
                            } else if (gamb < 2.4f) {
                                slb = 42.1f * gamb - 88.12f;
                            } else if (gamb < 2.6f) {
                                slb = 25.4f * gamb - 48.04f;
                            } else if (gamb < 2.88f) {
                                slb = 7.142857f * gamb - 0.571428f;
                            } else if (gamb < 3.6f) {
                                slb = 13.8888f * gamb - 20.f;
                            }
                        }
                        GammaValues g_ab; //gamma parameters
                        double pwrb = 1.0 / static_cast<double>(gamb);
                        Color::calcGamma(pwrb, slb, g_ab); // call to calcGamma with selected gamma and slope
                        Imagefloat *srcp = nullptr;
                        srcp = new Imagefloat(bfw, bfh);
  

#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic, 16) if (multiThread)
#endif

                        for (int i = 0; i < bfh; ++i)
                            for (int j = 0; j < bfw; ++j) {
                                float r = (double) tmpImage->r(i, j);
                                float g = (double) tmpImage->g(i, j);
                                float b = (double) tmpImage->b(i, j);
                                r = (Color::igammatab_srgb[r]) / 65535.f;
                                g = (Color::igammatab_srgb[g]) / 65535.f;
                                b = (Color::igammatab_srgb[b]) / 65535.f;
                                srcp->r(i, j) =  r;
                                srcp->g(i, j) =  g;
                                srcp->b(i, j) =  b;

                            }
            
#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int y = 0; y < bfh; ++y) {
                                int x = 0;
#if defined(__SSE2__) || defined(RT_SIMDE)

                            for (; x < bfw - 3; x += 4) {
                                STVFU(tmpImage->r(y, x), F2V(65536.f) * gammalog(LVFU(srcp->r(y, x)), F2V(gamr), F2V(slr), F2V(g_ar[3]), F2V(g_ar[4])));
                                STVFU(tmpImage->g(y, x), F2V(65536.f) * gammalog(LVFU(srcp->g(y, x)), F2V(gamg), F2V(slg), F2V(g_ag[3]), F2V(g_ag[4])));
                                STVFU(tmpImage->b(y, x), F2V(65536.f) * gammalog(LVFU(srcp->b(y, x)), F2V(gamb), F2V(slb), F2V(g_ab[3]), F2V(g_ab[4])));
                            }

#endif

                            for (; x < bfw; ++x) {
                                tmpImage->r(y, x) = 65536.f * gammalog(srcp->r(y, x), gamr, slr, g_ar[3], g_ar[4]);
                                tmpImage->g(y, x) = 65536.f * gammalog(srcp->g(y, x), gamg, slg, g_ag[3], g_ag[4]);
                                tmpImage->b(y, x) = 65536.f * gammalog(srcp->b(y, x), gamb, slb, g_ab[3], g_ab[4]);
                            }
                        }
                        delete srcp;
                        
                       if(params->locallab.spots.at(sp).smoothcietrc) {//invert color 
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic, 16) if (multiThread)
#endif

                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float rr = tmpImage->r(i, j); 
                                    float gg = tmpImage->g(i, j);
                                    float bb = tmpImage->b(i, j);
                                    if (rCurve) {
                                        setUnlessOOG(rr, rCurve[rr]);
                                    }
                                    if (gCurve) {
                                        setUnlessOOG(gg, gCurve[gg]);
                                    }
                                    if (bCurve) {
                                        setUnlessOOG(bb, bCurve[bb]);
                                    }
                                    tmpImage->r(i, j) = rr;
                                    tmpImage->g(i, j) = gg;
                                    tmpImage->b(i, j) = bb;
                               
                                }
                        }
                        
                        if(lp.smoothtrc > 0.f) {
                            tone_eqsmooth(this, tmpImage, lp, params->icm.workingProfile, sk, multiThread);//reduce Ev > 0 < 12
                        }
                        
                    }


                    if(lp.smoothciem == 6) {//Sigmoid - from Darktable
                        float middle_grey_contrast = params->locallab.spots.at(sp).contsig;
                        float contrast_skewness = params->locallab.spots.at(sp).skewsig;
                        float white_point_disp = params->locallab.spots.at(sp).whitsig;
                        float middle_grey = 0.01 * params->locallab.spots.at(sp).sourceGraycie;
                        float black_point =  xexpf(lp.blackevjz * std::log(2.f) + xlogf(middle_grey));
                      //  float white_pointsig = xexpf(lp.whiteevjz * std::log(2.f) + xlogf(middle_grey));//to adapt if need and remove slider whitsig
                      //  float dr = white_pointsig - black_point;
                      //  bool scale = lp.issmoothcie;//scale Yb mid_gray - WhiteEv and BlavkEv

                      //  if(scale) {//scale Yb mean luminance scene with white : dr and black
                      //      middle_grey = middle_grey * dr + black_point;
                      //  }

#ifdef _OPENMP
        #   pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                        for (int i = 0; i < bfh; ++i)
                            for (int j = 0; j < bfw; ++j) {
                                float r = tmpImage->r(i, j) / 65535.f;
                                float g = tmpImage->g(i, j) / 65535.f;
                                float b = tmpImage->b(i, j) / 65535.f;
                                float rout = 0.f;
                                float gout = 0.f;
                                float bout = 0.f;
                                sigmoid_main(r, g, b, rout, gout, bout, middle_grey_contrast, contrast_skewness, /*white_pointsig,*/ middle_grey, black_point, white_point_disp);
                                tmpImage->r(i, j) = 65535.f * rout;
                                tmpImage->g(i, j) = 65535.f * gout;
                                tmpImage->b(i, j) = 65535.f * bout;
                                
                            }
                    }
                    if(lp.smoothciem == 1) {
                        tone_eqsmooth(this, tmpImage, lp, params->icm.workingProfile, sk, multiThread);//reduce Ev > 0 < 12
                    } else if(lp.smoothciem == 2  || lp.smoothciem == 3 || lp.smoothciem == 4) {//  2 - only smmoth highlightd  - 3 - Tone mapping with slope and mid_grey

                        //TonemapFreeman - Copyright (c) 2023 Thatcher Freeman
                        float mid_gray = 0.01f * lp.sourcegraycie;//Mean luminance Yb Scene
                        float mid_gray_view = 0.01f * lp.targetgraycie;//Mean luminance Yb Viewing
                        lp.whiteevjz = LIM(lp.whiteevjz, 0.1f, 31.5f);//limit whiteEv to avoid crash
                        float white_point =  xexpf(lp.whiteevjz * std::log(2.f) + xlogf(mid_gray));//lp.whiteevjz  White_Ev
                        lp.blackevjz = LIM(lp.blackevjz, -15.5f, -0.2f);//limit BlackEv to avoid crash
                        float black_point =  xexpf(lp.blackevjz * std::log(2.f) + xlogf(mid_gray));//lp.blackevjz  Black_Ev
                        bool rolloff = true;//only soften highlights
                        float slopegray = 1.f;//slopegray between 0.8 and 1.6 - lineary light the shadows by the user - the gamma is calculated according to slope and the characteristics of the image DR, White, Black
                        float slopegrayr = 1.f;
                        float slopegrayg = 1.f;
                        float slopegrayb = 1.f;
                        int mode = 1;
                        float slopsmoot = 1.f - ((float) params->locallab.spots.at(sp).slopesmo - 1.f);//modify response so when increase slope the grays are becoming lighter
                        float slopsmootr = 1.f - ((float) params->locallab.spots.at(sp).slopesmor - 1.f);
                        float slopsmootg = 1.f - ((float) params->locallab.spots.at(sp).slopesmog - 1.f);
                        float slopsmootb = 1.f - ((float) params->locallab.spots.at(sp).slopesmob - 1.f);
                        slopeg = params->locallab.spots.at(sp).slopesmog; 
                        linkrgb = params->locallab.spots.at(sp).smoothcielnk;
                        float smooththreshold = params->locallab.spots.at(sp).smoothcieth;
                        bool takeyb = params->locallab.spots.at(sp).smoothcieyb;
                        bool lummod = params->locallab.spots.at(sp).smoothcielum;
                        bool lumhigh = params->locallab.spots.at(sp).smoothciehigh;
                        float maxsl= 4.f;//maximum real slope
                        float minslider = 0.01f;//minimum slider value > 0.f
                        float aa = (1.9f - maxsl) / (0.1f - minslider);//interpolation : 1.9f slope value for slider = 0.1f
                        float bb = 1.9f - 0.1f * aa;
                        
                        if(lp.smoothciem == 3) {//slope activ, only with choice gamma - slope - based
                            rolloff = false;//allows tone-mapping slope
                            if(slopsmoot < 0.1f) {
                                slopsmoot = aa * slopsmoot + bb;
                            }
                            slopegray = slopsmoot;
                            slopegrayr = slopsmoot;
                            slopegrayg = slopsmoot;
                            slopegrayb = slopsmoot;
                            mode = 3;
                        }//modify slope
                        if(lp.smoothciem == 4) {//levels
                            rolloff = false;//allows tone-mapping slope
                            if(slopsmootr < 0.1f) {
                                slopsmootr = aa * slopsmootr + bb;
                            }
                            slopegrayr = slopsmootr;
                            if(slopsmootg < 0.1f) {
                                slopsmootg = aa * slopsmootg + bb;
                            }
                            slopegrayg = slopsmootg;
                            if(slopsmootb < 0.1f) {
                                slopsmootb = aa * slopsmootb + bb;
                            }
                            slopegrayb = slopsmootb;
                            mode = 4;
                        }
                        LUTf lut(65536, LUT_CLIP_OFF);//take from Alberto Griggio
                        LUTf lutr(65536, LUT_CLIP_OFF);
                        LUTf lutg(65536, LUT_CLIP_OFF);
                        LUTf lutb(65536, LUT_CLIP_OFF);
                        
                        bool scale = lp.issmoothcie;//scale Yb mid_gray - WhiteEv and BlavkEv
                        bool limslope = lumhigh;
                        tonemapFreeman(slopegray, slopegrayr, slopegrayg, slopegrayb, white_point, black_point, mid_gray, mid_gray_view, rolloff, smooththreshold, limslope, lut, lutr, lutg, lutb, mode, scale, takeyb);
                        if(lp.smoothciem == 4) {
                            if(lummod  && lp.smoothciem == 4) {//luminosity mode by Lab conversion
 #ifdef _OPENMP
        #pragma omp parallel for
#endif
                                for (int y = 0; y < bfh ; ++ y) {
                                    for (int x = 0; x < bfw ; ++x) {
                                        float r = tmpImage->r(y, x);
                                        float g = tmpImage->g(y, x);
                                        float b = tmpImage->b(y, x);
                                        //convert to Lab to get a&b before RGB
                                        float xx = toxyz[0][0] * r + toxyz[0][1] * g + toxyz[0][2] * b;
                                        float yy = toxyz[1][0] * r + toxyz[1][1] * g + toxyz[1][2] * b;
                                        float zz = toxyz[2][0] * r + toxyz[2][1] * g + toxyz[2][2] * b;

                                        float fx = xx < MAXVALF ? Color::cachef[xx] : 327.68f * std::cbrt(xx / MAXVALF);
                                        float fy = yy < MAXVALF ? Color::cachef[yy] : 327.68f * std::cbrt(yy / MAXVALF);
                                        float fz = zz < MAXVALF ? Color::cachef[zz] : 327.68f * std::cbrt(zz / MAXVALF);

                                        float a_1 = 500.0f * (fx - fy);
                                        float b_1 = 200.0f * (fy - fz);
                                        float rNew = 65535.f * lutr[tmpImage->r(y, x)];
                                        float gNew = 65535.f * lutg[tmpImage->g(y, x)];
                                        float bNew = 65535.f * lutb[tmpImage->b(y, x)];
                                        r += (rNew - r) * equalR;
                                        g += (gNew - g) * equalG;
                                        b += (bNew - b) * equalB;
                                        float newy = toxyz[1][0] * r + toxyz[1][1] * g + toxyz[1][2] * b;
                                        float L_2 = newy <= MAXVALF ? Color::cachefy[newy] : 327.68f * (116.f * xcbrtf(newy / MAXVALF) - 16.f);
                                        float x_, y_, z_;
                                        //calculate RGB with L_2 and old value of a and b
                                        Color::Lab2XYZ(L_2, a_1, b_1, x_, y_, z_) ;
                                        if (params->locallab.spots.at(sp).avoidgamutMethod != "NONE") {//possibility of deactivating to see usefulness. is it necessary? 
                                            Color::gamutmap(x_, y_, z_, wp);//if none disabled
                                        }
                                        Color::xyz2rgb(x_, y_, z_, r, g, b, wip);
                                        tmpImage->r(y, x) = r;
                                        tmpImage->g(y, x) = g;
                                        tmpImage->b(y, x) = b;
                                    }
                                }
                            } else {//RGG case
 #ifdef _OPENMP
        #pragma omp parallel for
#endif
                                for (int y = 0; y < bfh ; ++ y) {//apply Lut tone-mapping or smooth: thanks to Alberto - gain time.
                                    for (int x = 0; x < bfw ; ++x) {
                                        tmpImage->r(y, x) = 65535.f * lutr[tmpImage->r(y, x)];
                                        tmpImage->g(y, x) = 65535.f * lutg[tmpImage->g(y, x)];
                                        tmpImage->b(y, x) = 65535.f * lutb[tmpImage->b(y, x)];
                                    }
                                }
                            }
                        } else {//Slope case
 #ifdef _OPENMP
        #pragma omp parallel for
#endif
                            for (int y = 0; y < bfh ; ++ y) {//apply Lut tone-mapping or smooth: thanks to Alberto - gain time.
                                for (int x = 0; x < bfw ; ++x) {
                                    tmpImage->r(y, x) = 65535.f * lut[tmpImage->r(y, x)];
                                    tmpImage->g(y, x) = 65535.f * lut[tmpImage->g(y, x)];
                                    tmpImage->b(y, x) = 65535.f * lut[tmpImage->b(y, x)];
                                }
                            }
                        }
                        
                       if(params->locallab.spots.at(sp).smoothcieinv && lp.smoothciem == 4) {//invert color with RGB slope
#ifdef _OPENMP
        #pragma omp parallel for schedule(dynamic, 16) if (multiThread)
#endif

                            for (int i = 0; i < bfh; ++i)
                                for (int j = 0; j < bfw; ++j) {
                                    float rr = tmpImage->r(i, j); 
                                    float gg = tmpImage->g(i, j);
                                    float bb = tmpImage->b(i, j);
                                    if (rCurve) {
                                        setUnlessOOG(rr, rCurve[rr]);
                                    }
                                    if (gCurve) {                                    
                                        setUnlessOOG(gg, gCurve[gg]);
                                    }
                                    if (bCurve) {                                   
                                        setUnlessOOG(bb, bCurve[bb]);
                                    }
                                    tmpImage->r(i, j) = rr;
                                    tmpImage->g(i, j) = gg;
                                    tmpImage->b(i, j) = bb;                              
                                }
                        }
                        
                    } 
                    rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile);

                    delete tmpImage;
                    delete tmpImage2;
                    delete tmpImagelog;
                }

                if (params->locallab.spots.at(sp).expcie) {
                        ImProcFunctions::ciecamloc_02float(lp, sp, bufexpfin.get(), bfw, bfh, 0, sk, cielocalcurve, localcieutili, cielocalcurve2, localcieutili2, jzlocalcurve, localjzutili, czlocalcurve, localczutili, czjzlocalcurve, localczjzutili, locchCurvejz, lochhCurvejz, loclhCurvejz, HHcurvejz, CHcurvejz, LHcurvejz, locwavCurvejz, locwavutilijz, maxicam, contsig, lightsig);
                }
                
                if(lp.midtcie != 0 && lp.midtmet == 2) {
                    Imagefloat *tmpImageaft = nullptr;
                    tmpImageaft = new Imagefloat(bfw, bfh);
                    
                    lab2rgb(*bufexpfin, *tmpImageaft, params->icm.workingProfile);
                    ImProcFunctions::tone_eqcam(this, tmpImageaft, lp.midtcie, params->icm.workingProfile, sk, multiThread);
                    
                    rgb2lab(*tmpImageaft, *bufexpfin, params->icm.workingProfile);

                    delete tmpImageaft;
                }
                
            }
            
            if (lp.strgradcie != 0.f) {

                    struct grad_params gp;
                    calclocalGradientParams(call, lp, gp, ystart, xstart, yend, xend,  bfw, bfh, oW, oH, tX, tY, tW, tH, 15, sk, fw, fh, cx, cy, ksk, kskx, kbh, kbw);
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufexpfin->L[ir][jr] *= ImProcFunctions::calcGradientFactor(gp, cx*kskx + kbw*jr, cy*ksk + kbh*ir);
                        }
                    }
            }

            if (lp.enacieMask && lp.recothrcie != 1.f) {
                float recoth = lp.recothrcie;

                if (lp.recothrcie < 1.f) {
                    recoth = -1.f * recoth + 2.f;
                }

                float hig = lp.higthrcie;
                float low = lp.lowthrcie;
                float decay = lp.decaycie;
                bool invmask = false;
                maskrecov(bufexpfin.get(), original, bufmaskorigcie.get(), bfh, bfw, ystart, xstart, hig, low, recoth, decay, invmask, sk, multiThread);
            }


            float radcie = 0.30f;
            bool exec_lc = true;
            if(lp.moka == 1  && lp.sursouci == 4) {//disable local contrast if user do not want to use ciecam and Cam16 (I make this choice)
                exec_lc = false;
            }
            if(lp.moka == 1) { //cam16
                radcie = 0.01 * params->locallab.spots.at(sp).detailcie;
            } else if(lp.moka == 2) { //Jz
                radcie = 0.01 * params->locallab.spots.at(sp).detailciejz;
            }
            if(radcie > 0.f && exec_lc) {
            //local contrast 
                    //ampirical ponderation, to verify
                    float surrsour_ampl_str = 1.f;//amount
                    float surrsour_ampl_dark = 1.f;//dark
                    float surrsour_ampl_radius = 1.f;//radius
                    if(lp.sursouci == 0) {//Average
                        surrsour_ampl_str = 0.9f;
                        surrsour_ampl_dark = 1.f;
                        surrsour_ampl_radius = 1.f;
                    } else  if(lp.sursouci == 1) {//Dim
                        surrsour_ampl_str = 1.f;
                        surrsour_ampl_dark = 1.1f;
                        surrsour_ampl_radius = 1.05f;
                    } else  if(lp.sursouci == 2) {//Dark
                        surrsour_ampl_str = 1.2f;
                        surrsour_ampl_dark = 1.2f;
                        surrsour_ampl_radius = 1.1f;
                    } else  if(lp.sursouci == 3) {//Ex Dark
                        surrsour_ampl_str = 1.4f;
                        surrsour_ampl_radius = 1.2f;
                        surrsour_ampl_dark = 1.5f;
                    } else  if(lp.sursouci == 4 && lp.moka == 2) {//less than average for Jz
                        surrsour_ampl_str = 0.9f;
                        surrsour_ampl_radius = 0.9f;
                        surrsour_ampl_dark = 1.f;
                    }
                    LocalContrastParams localContrastParams;
                    LocallabParams locallabparams;
                    localContrastParams.enabled = true;
                    localContrastParams.radius = rtengine::min((double) surrsour_ampl_radius * 80., 100.);
                    localContrastParams.amount = rtengine::min(radcie * surrsour_ampl_str, 1.f);
                    localContrastParams.darkness = rtengine::min((double) surrsour_ampl_dark * 1., 3.);
                    localContrastParams.lightness = 1.;
                    bool fftwlc = false;

                    ImProcFunctions::localContrast(bufexpfin.get(), bufexpfin->L, localContrastParams, fftwlc, sk);
            }
            
            if (params->locallab.spots.at(sp).bwcie) {
#ifdef _OPENMP
                    #pragma omp parallel for schedule(dynamic,16) if (multiThread)
#endif

                    for (int ir = 0; ir < bfh; ir++) {
                        for (int jr = 0; jr < bfw; jr++) {
                            bufexpfin->a[ir][jr] = 0.f;
                            bufexpfin->b[ir][jr] = 0.f;
                        }
                    }
            }
            

            const float repart = 1.0 - 0.01 * params->locallab.spots.at(sp).reparcie;
            int bw = bufexporig->W;
            int bh = bufexporig->H;

#ifdef _OPENMP
            #pragma omp parallel for schedule(dynamic,16) if(multiThread)
#endif

            for (int x = 0; x < bh; x++) {
                for (int y = 0; y < bw; y++) {
                    bufexpfin->L[x][y] = intp(repart, bufexporig->L[x][y], bufexpfin->L[x][y]);
                    bufexpfin->a[x][y] = intp(repart, bufexporig->a[x][y], bufexpfin->a[x][y]);
                    bufexpfin->b[x][y] = intp(repart, bufexporig->b[x][y], bufexpfin->b[x][y]);
                }
            }

            if (lp.recothrcie >= 1.f) {
                transit_shapedetect2(sp, 0.f, 0.f, call, 31, bufexporig.get(), bufexpfin.get(), originalmaskcie.get(), hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
            } else {
                transit_shapedetect2(sp, 0.f, 0.f, call, 31, bufexporig.get(), bufexpfin.get(), nullptr, hueref, chromaref, lumaref, sobelref, 0.f, nullptr, lp, original, transformed, nullptr, LocalLabGradientMode::STANDARD, cx, cy, sk);
            }

            if (lp.recur) {
                original->CopyFrom(transformed, multiThread);
                float avge;
                calc_ref(sp, original, transformed, 0, 0, original->W, original->H, sk, huerefblur, chromarefblur, lumarefblur, hueref, chromaref, lumaref, sobelref, avge, locwavCurveden, locwavdenutili);
            }
        }
    }
    int bw = transformed->W;
    int bh = transformed->H;
    bool notzero = false; //verify that RGB values are > 0.f issue 7121 to avoid crash. Could perhaps be used in other cases as RGB curves (main)
    bool notlaplacian = false;//no use of strong Laplacian

    float epsi = 0.000001f;

    if((lp.laplacexp > 1.f && lp.exposena) || (lp.strng > 2.f && lp.sfena) || (lp.exposena && lp.expcomp != 0.f && params->dirpyrequalizer.enabled)){//strong Laplacian
        notlaplacian = true;
    }

    if(((lp.laplacexp > 0.f && lp.laplacexp <= 1.f) && lp.exposena && lp.blac == 0.f)) { // use Laplacian with very small values
        notzero = true;
    } else if ((lp.laplacexp > 0.f && lp.laplacexp <= 1.f) && lp.exposena && lp.blac != 0.f) {//for curvelocal simplebasecurve with black
        notlaplacian = true;
    }

    if(ghsactiv && params->locallab.spots.at(sp).ghs_BLP > 0.) {//avoid crash in some rare cases when BP > 0 and datas image near zero.
        notlaplacian = true;
    }

    ToneCurveMode curveMode = params->toneCurve.curveMode;//Tone curve does not allow negative values
    if((curveMode == ToneCurveMode::PERCEPTUAL) || (curveMode == ToneCurveMode::STD) || (curveMode == ToneCurveMode::WEIGHTEDSTD)  || (curveMode == ToneCurveMode::FILMLIKE) || (curveMode == ToneCurveMode::SATANDVALBLENDING) || (curveMode == ToneCurveMode::LUMINANCE)) {
        notzero = true;
    }

    ToneCurveMode curveMode2 = params->toneCurve.curveMode2;//Tone curve does not allow negative values
    if((curveMode2 == ToneCurveMode::PERCEPTUAL) || (curveMode2 == ToneCurveMode::STD) || (curveMode2 == ToneCurveMode::WEIGHTEDSTD)  || (curveMode2 == ToneCurveMode::FILMLIKE) || (curveMode2 == ToneCurveMode::SATANDVALBLENDING) || (curveMode2 == ToneCurveMode::LUMINANCE)) {
        notzero = true;
    }
    
    if(params->rgbCurves.enabled || params->hsvequalizer.enabled  || params->chmixer.enabled  || params->colorToning.enabled ) {//rgb curves, HSV, Channel mixer, Color Toning does not allow negative values. Perhaps others cases ?
        notzero = true;
    }

    if(notlaplacian || notzero) {//allows memory and conversion labrgb only in these cases
        const std::unique_ptr<Imagefloat> prov1(new Imagefloat(bw, bh));
        lab2rgb(*transformed, *prov1, params->icm.workingProfile);

        if(notlaplacian) {//clip value above 65535.f and > epsilon when Contrast attenuator with high values Laplacian or Original Retinex 
#ifdef _OPENMP
        #pragma omp parallel for
#endif
            for (int i = 0; i < bh; ++i)
                for (int j = 0; j < bw; ++j) {
                    prov1->r(i, j) = clipR(rtengine::max(prov1->r(i, j), epsi));
                    prov1->g(i, j) = clipR(rtengine::max(prov1->g(i, j), epsi));
                    prov1->b(i, j) = clipR(rtengine::max(prov1->b(i, j), epsi)); 
                }
        } else if(notzero) {//standard case only with small values Laplace no clip
#ifdef _OPENMP
        #pragma omp parallel for
#endif
            for (int i = 0; i < bh; ++i)
                for (int j = 0; j < bw; ++j) {
                    prov1->r(i, j) = rtengine::max(prov1->r(i, j), epsi);
                    prov1->g(i, j) = rtengine::max(prov1->g(i, j), epsi);
                    prov1->b(i, j) = rtengine::max(prov1->b(i, j), epsi); 
                }
        }
        rgb2lab(*prov1, *transformed, params->icm.workingProfile);
    }


// Gamut and Munsell control - very important do not deactivated to avoid crash
    avoidcolshi(lp, sp, transformed, reserved, cy, cx, sk);
}

}
