diff --git a/rtdata/languages/default b/rtdata/languages/default index 3d49170ce..0d7d0aa66 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -2233,10 +2233,11 @@ TP_LOCALLAB_CURVNONE;Disable curves TP_LOCALLAB_DARKRETI;Darkness TP_LOCALLAB_DEHAFRA;Dehaze TP_LOCALLAB_DEHAZ;Strength +TP_LOCALLAB_DEHAZ_TOOLTIP;Negative values add haze TP_LOCALLAB_DELTAEC;Mask DeltaE Image TP_LOCALLAB_DENOIS;Denoise TP_LOCALLAB_DEPTH;Depth -TP_LOCALLAB_DETAIL;Detail +TP_LOCALLAB_DETAIL;Detail (Guided Filter) TP_LOCALLAB_DETAILSH;Details TP_LOCALLAB_DETAILTHR;Detail threshold Luminance Chroma (DCT) TP_LOCALLAB_DUPLSPOTNAME;Copy diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index cc1cb151a..21f0f2114 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -402,6 +402,7 @@ public: void BadpixelsLab(LabImage * lab, double radius, int thresh, float chrom); void dehaze(Imagefloat *rgb, const procparams::DehazeParams &dehazeParams); + void dehazeloc(Imagefloat *rgb, const procparams::DehazeParams &dehazeParams); void ToneMapFattal02(Imagefloat *rgb, const procparams::FattalToneMappingParams &fatParams, int detail_level); void localContrast(LabImage *lab, float **destination, const procparams::LocalContrastParams &localContrastParams, bool fftwlc, double scale); void colorToningLabGrid(LabImage *lab, int xstart, int xend, int ystart, int yend, bool MultiThread); diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index e52164187..69cb3b17c 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -40,6 +40,8 @@ #include "procparams.h" #include "rescale.h" #include "rt_math.h" +//#define BENCHMARK +#include "StopWatch.h" #include "../rtgui/options.h" @@ -57,15 +59,18 @@ float normalize(Imagefloat *rgb, bool multithread) #ifdef _OPENMP #pragma omp parallel for reduction(max:maxval) schedule(dynamic, 16) if (multithread) #endif + for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { maxval = max(maxval, rgb->r(y, x), rgb->g(y, x), rgb->b(y, x)); } } + maxval = max(maxval * 2.f, 65535.f); #ifdef _OPENMP #pragma omp parallel for schedule(dynamic, 16) if (multithread) #endif + for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { rgb->r(y, x) /= maxval; @@ -73,6 +78,7 @@ float normalize(Imagefloat *rgb, bool multithread) rgb->b(y, x) /= maxval; } } + return maxval; } @@ -80,10 +86,12 @@ void restore(Imagefloat *rgb, float maxval, bool multithread) { const int W = rgb->getWidth(); const int H = rgb->getHeight(); + if (maxval > 0.f && maxval != 1.f) { #ifdef _OPENMP -# pragma omp parallel for if (multithread) + # pragma omp parallel for if (multithread) #endif + for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { rgb->r(y, x) *= maxval; @@ -102,8 +110,10 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr #ifdef _OPENMP #pragma omp parallel for if (multithread) #endif + for (int y = 0; y < H; y += patchsize) { const int pH = min(y + patchsize, H); + for (int x = 0; x < W; x += patchsize) { float minR = RT_INFINITY_F; float minG = RT_INFINITY_F; @@ -114,21 +124,26 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr vfloat minBv = F2V(minB); #endif const int pW = min(x + patchsize, W); + for (int yy = y; yy < pH; ++yy) { int xx = x; #ifdef __SSE2__ + for (; xx < pW - 3; xx += 4) { minRv = vminf(minRv, LVFU(R[yy][xx])); minGv = vminf(minGv, LVFU(G[yy][xx])); minBv = vminf(minBv, LVFU(B[yy][xx])); } + #endif + for (; xx < pW; ++xx) { minR = min(minR, R[yy][xx]); minG = min(minG, G[yy][xx]); minB = min(minB, B[yy][xx]); } } + #ifdef __SSE2__ minR = min(minR, vhmin(minRv)); minG = min(minG, vhmin(minGv)); @@ -136,13 +151,14 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr #endif float val = min(minR / ambient[0], minG / ambient[1], minB / ambient[2]); val = 1.f - strength * LIM01(val); + for (int yy = y; yy < pH; ++yy) { std::fill(dst[yy] + x, dst[yy] + pW, val); } } } - return (W / patchsize + ((W % patchsize) > 0)) * (H / patchsize + ((H % patchsize) > 0)); + return (W / patchsize + ((W % patchsize) > 0)) * (H / patchsize + ((H % patchsize) > 0)); } int get_dark_channel_downsized(const array2D &R, const array2D &G, const array2D &B, const array2D &dst, int patchsize, bool multithread) @@ -153,23 +169,27 @@ int get_dark_channel_downsized(const array2D &R, const array2D &G, #ifdef _OPENMP #pragma omp parallel for if (multithread) #endif + for (int y = 0; y < H; y += patchsize) { const int pH = min(y + patchsize, H); + for (int x = 0; x < W; x += patchsize) { float val = RT_INFINITY_F; const int pW = min(x + patchsize, W); + for (int xx = x; xx < pW; ++xx) { for (int yy = y; yy < pH; ++yy) { val = min(val, R[yy][xx], G[yy][xx], B[yy][xx]); } } + for (int yy = y; yy < pH; ++yy) { std::fill(dst[yy] + x, dst[yy] + pW, val); } } } - return (W / patchsize + ((W % patchsize) > 0)) * (H / patchsize + ((H % patchsize) > 0)); + return (W / patchsize + ((W % patchsize) > 0)) * (H / patchsize + ((H % patchsize) > 0)); } float estimate_ambient_light(const array2D &R, const array2D &G, const array2D &B, const array2D &dark, int patchsize, int npatches, float ambient[3]) @@ -180,6 +200,7 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c float darklim = RT_INFINITY_F; { std::vector p; + for (int y = 0; y < H; y += patchsize) { for (int x = 0; x < W; x += patchsize) { if (!OOG(dark[y][x], 1.f - 1e-5f)) { @@ -187,6 +208,7 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c } } } + const int pos = p.size() * 0.95; std::nth_element(p.begin(), p.begin() + pos, p.end()); darklim = p[pos]; @@ -212,17 +234,18 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c { std::vector l; l.reserve(patches.size() * patchsize * patchsize); - + for (auto &p : patches) { - const int pW = min(p.first+patchsize, W); - const int pH = min(p.second+patchsize, H); - + const int pW = min(p.first + patchsize, W); + const int pH = min(p.second + patchsize, H); + for (int y = p.second; y < pH; ++y) { for (int x = p.first; x < pW; ++x) { l.push_back(R[y][x] + G[y][x] + B[y][x]); } } } + const int pos = l.size() * 0.95; std::nth_element(l.begin(), l.begin() + pos, l.end()); bright_lim = l[pos]; @@ -230,15 +253,17 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c double rr = 0, gg = 0, bb = 0; int n = 0; + for (auto &p : patches) { - const int pW = min(p.first+patchsize, W); - const int pH = min(p.second+patchsize, H); - + const int pW = min(p.first + patchsize, W); + const int pH = min(p.second + patchsize, H); + for (int y = p.second; y < pH; ++y) { for (int x = p.first; x < pW; ++x) { float r = R[y][x]; float g = G[y][x]; float b = B[y][x]; + if (r + g + b >= bright_lim) { rr += r; gg += g; @@ -248,6 +273,7 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c } } } + n = std::max(n, 1); ambient[0] = rr / n; ambient[1] = gg / n; @@ -433,4 +459,167 @@ void ImProcFunctions::dehaze(Imagefloat *img, const DehazeParams &dehazeParams) } } + + +void ImProcFunctions::dehazeloc(Imagefloat *img, const DehazeParams &dehazeParams) +{ + //J.Desmis 12 2019 - this version derived from ART, is slower than the main from maximimum 10% - probably use of SSE + //Probably Ingo could solved this problem in some times + BENCHFUN + if (!dehazeParams.enabled || dehazeParams.strength == 0.0) { + return; + } + + + + const float maxChannel = normalize(img, multiThread); + + const int W = img->getWidth(); + const int H = img->getHeight(); + const float strength = LIM01(float(std::abs(dehazeParams.strength)) / 100.f * 0.9f); + const bool add_haze = dehazeParams.strength < 0; + + if (settings->verbose) { + std::cout << "dehaze: strength = " << strength << std::endl; + } + + array2D dark(W, H); + + int patchsize = max(int(5 / scale), 2); + float ambient[3]; + float maxDistance = 0.f; + + { + array2D& R = dark; // R and dark can safely use the same buffer, which is faster and reduces memory allocations/deallocations + array2D G(W, H); + array2D B(W, H); + extract_channels(img, R, G, B, patchsize, 1e-1, multiThread); + + { + constexpr int sizecap = 200; + const float r = static_cast(W) / static_cast(H); + const int hh = r >= 1.f ? sizecap : sizecap / r; + const int ww = r >= 1.f ? sizecap * r : sizecap; + + if (W <= ww && H <= hh) { + // don't rescale small thumbs + array2D D(W, H); + const int npatches = get_dark_channel_downsized(R, G, B, D, 2, multiThread); + maxDistance = estimate_ambient_light(R, G, B, D, patchsize, npatches, ambient); + } else { + array2D RR(ww, hh); + array2D GG(ww, hh); + array2D BB(ww, hh); + rescaleNearest(R, RR, multiThread); + rescaleNearest(G, GG, multiThread); + rescaleNearest(B, BB, multiThread); + array2D D(ww, hh); + + const int npatches = get_dark_channel_downsized(RR, GG, BB, D, 2, multiThread); + maxDistance = estimate_ambient_light(RR, GG, BB, D, patchsize, npatches, ambient); + } + } + + if (min(ambient[0], ambient[1], ambient[2]) < 0.01f) { + if (settings->verbose) { + std::cout << "dehaze: no haze detected" << std::endl; + } + + restore(img, maxChannel, multiThread); + return; // probably no haze at all + } + + patchsize = max(max(W, H) / 600, 2); + + if (settings->verbose) { + std::cout << "dehaze: ambient light is " + << ambient[0] << ", " << ambient[1] << ", " << ambient[2] + << std::endl; + } + + get_dark_channel(R, G, B, dark, patchsize, ambient, true, multiThread, strength); + } + + + const int radius = patchsize * 4; + constexpr float epsilon = 1e-5f; + + array2D guideB(W, H, img->b.ptrs, ARRAY2D_BYREFERENCE); + guidedFilter(guideB, dark, dark, radius, epsilon, multiThread); + + if (settings->verbose) { + std::cout << "dehaze: max distance is " << maxDistance << std::endl; + } + + const float depth = -float(dehazeParams.depth) / 100.f; + const float teps = 1e-6f; + const float t0 = max(teps, std::exp(depth * maxDistance)); + + const bool luminance = dehazeParams.luminance; + const TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); + + const float ambientY = Color::rgbLuminance(ambient[0], ambient[1], ambient[2], ws); +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + + for (int y = 0; y < H; ++y) { + int x = 0; + for (; x < W; ++x) { + // ensure that the transmission is such that to avoid clipping... + float rgb[3] = { img->r(y, x), img->g(y, x), img->b(y, x) }; + // ... t >= tl to avoid negative values + float tl = 1.f - min(rgb[0] / ambient[0], rgb[1] / ambient[1], rgb[2] / ambient[2]); + // // ... t >= tu to avoid values > 1 + // float tu = t0 - teps; + // for (int c = 0; c < 3; ++c) { + // if (ambient[c] < 1) { + // tu = max(tu, (rgb[c] - ambient[c])/(1.f - ambient[c])); + // } + // } + float &ir = img->r(y, x); + float &ig = img->g(y, x); + float &ib = img->b(y, x); + const float mt = max(dark[y][x], t0, tl + teps); + + if (dehazeParams.showDepthMap) { + img->r(y, x) = img->g(y, x) = img->b(y, x) = LIM01(1.f - mt) * maxChannel; + } else if (luminance) { + float Y = Color::rgbLuminance(img->r(y, x), img->g(y, x), img->b(y, x), ws); + float YY = (Y - ambientY) / mt + ambientY; + + if (Y > 1e-5f) { + if (add_haze) { + YY = Y + Y - YY; + } + + float f = YY / Y; + ir = rgb[0] * f; + ig = rgb[1] * f; + ib = rgb[2] * f; + + } + } else { + float r = ((rgb[0] - ambient[0]) / mt + ambient[0]); + float g = ((rgb[1] - ambient[1]) / mt + ambient[1]); + float b = ((rgb[2] - ambient[2]) / mt + ambient[2]); + + if (add_haze) { + ir += (ir - r); + ig += (ig - g); + ib += (ib - b); + } else { + ir = r; + ig = g; + ib = b; + } + + } + } + } + + restore(img, maxChannel, multiThread); + +} + } // namespace rtengine diff --git a/rtengine/iplocallab.cc b/rtengine/iplocallab.cc index a9af032be..644c99599 100644 --- a/rtengine/iplocallab.cc +++ b/rtengine/iplocallab.cc @@ -10836,7 +10836,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o // } //&& lp.retiena - if (lp.dehaze > 0 && lp.retiena) { + if (lp.dehaze != 0 && lp.retiena) { int ystart = std::max(static_cast(lp.yc - lp.lyT) - cy, 0); int yend = std::min(static_cast(lp.yc + lp.ly) - cy, original->H); int xstart = std::max(static_cast(lp.xc - lp.lxL) - cx, 0); @@ -10864,7 +10864,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o //calc dehaze Imagefloat *tmpImage = nullptr; - if (lp.dehaze > 0) { + if (lp.dehaze != 0) { DehazeParams dehazeParams; dehazeParams.enabled = true; dehazeParams.strength = lp.dehaze; @@ -10873,7 +10873,7 @@ void ImProcFunctions::Lab_Local(int call, int sp, float** shbuffer, LabImage * o dehazeParams.luminance = params->locallab.spots.at(sp).lumonly; tmpImage = new Imagefloat(bfw, bfh); lab2rgb(*bufexpfin, *tmpImage, params->icm.workingProfile); - dehaze(tmpImage, dehazeParams); + dehazeloc(tmpImage, dehazeParams); rgb2lab(*tmpImage, *bufexpfin, params->icm.workingProfile); delete tmpImage; diff --git a/rtgui/locallab.cc b/rtgui/locallab.cc index ee05d037b..929820e1e 100644 --- a/rtgui/locallab.cc +++ b/rtgui/locallab.cc @@ -421,7 +421,7 @@ chrrt(Gtk::manage(new Adjuster(M("TP_LOCALLAB_CHRRT"), 0.0, 100.0, 0.1, 0.0))), neigh(Gtk::manage(new Adjuster(M("TP_LOCALLAB_NEIGH"), MINNEIGH, MAXNEIGH, 0.5, 50., nullptr, nullptr, &retiSlider2neigh, &retiNeigh2Slider))), vart(Gtk::manage(new Adjuster(M("TP_LOCALLAB_VART"), 0.1, 500., 0.1, 150.))), offs(Gtk::manage(new Adjuster(M("TP_LOCALLAB_OFFS"), -16386., 32768., 1., 0.))), -dehaz(Gtk::manage(new Adjuster(M("TP_LOCALLAB_DEHAZ"), 0, 100, 1, 0))), +dehaz(Gtk::manage(new Adjuster(M("TP_LOCALLAB_DEHAZ"), -100, 100, 1, 0))), depth(Gtk::manage(new Adjuster(M("TP_LOCALLAB_DEPTH"), 0, 100, 1, 25))), sensih(Gtk::manage(new Adjuster(M("TP_LOCALLAB_SENSIH"), 0, 100, 1, 60))), softradiusret(Gtk::manage(new Adjuster(M("TP_LOCALLAB_SOFTRETI"), -10.0, 1000.0, 0.5, 40.))), @@ -2349,6 +2349,7 @@ pe(nullptr) chrrt->setAdjusterListener(this); if (showtooltip) { + dehaz->set_tooltip_text(M("TP_LOCALLAB_DEHAZ_TOOLTIP")); sensih->set_tooltip_text(M("TP_LOCALLAB_SENSIH_TOOLTIP")); }