diff --git a/rtdata/languages/default b/rtdata/languages/default index 1368b43f8..dd5c7e697 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -732,6 +732,10 @@ HISTORY_MSG_492;RGB Curves HISTORY_MSG_493;L*a*b* Adjustments HISTORY_MSG_CLAMPOOG;Clip out-of-gamut colors HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction +HISTORY_MSG_DEHAZE_DEPTH;Dehaze - Depth +HISTORY_MSG_DEHAZE_ENABLED;Haze Removal +HISTORY_MSG_DEHAZE_SHOW_DEPTH_MAP;Dehaze - Show depth map +HISTORY_MSG_DEHAZE_STRENGTH;Dehaze - Strength HISTORY_MSG_DUALDEMOSAIC_CONTRAST;Dual demosaic - Contrast threshold HISTORY_MSG_DUALDEMOSAIC_AUTO_CONTRAST;Dual demosaic - Auto threshold HISTORY_MSG_HISTMATCHING;Auto-matched tone curve @@ -959,6 +963,7 @@ PARTIALPASTE_CROP;Crop PARTIALPASTE_DARKFRAMEAUTOSELECT;Dark-frame auto-selection PARTIALPASTE_DARKFRAMEFILE;Dark-frame file PARTIALPASTE_DEFRINGE;Defringe +PARTIALPASTE_DEHAZE;Haze removal PARTIALPASTE_DETAILGROUP;Detail Settings PARTIALPASTE_DIALOGLABEL;Partial paste processing profile PARTIALPASTE_DIRPYRDENOISE;Noise reduction @@ -1510,6 +1515,10 @@ TP_DARKFRAME_LABEL;Dark-Frame TP_DEFRINGE_LABEL;Defringe TP_DEFRINGE_RADIUS;Radius TP_DEFRINGE_THRESHOLD;Threshold +TP_DEHAZE_DEPTH;Depth +TP_DEHAZE_LABEL;Haze Removal +TP_DEHAZE_SHOW_DEPTH_MAP;Show Depth Map +TP_DEHAZE_STRENGTH;Strength TP_DIRPYRDENOISE_CHROMINANCE_AMZ;Auto multi-zones TP_DIRPYRDENOISE_CHROMINANCE_AUTOGLOBAL;Automatic global TP_DIRPYRDENOISE_CHROMINANCE_AUTOGLOBAL_TOOLTIP;Try to evaluate chroma noise\nBe careful, this calculation is average, and is quite subjective ! diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 759316e33..f610cc8b0 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -128,6 +128,7 @@ set(RTENGINESOURCEFILES vng4_demosaic_RT.cc ipsoftlight.cc guidedfilter.cc + ipdehaze.cc ) if(LENSFUN_HAS_LOAD_DIRECTORY) diff --git a/rtengine/color.h b/rtengine/color.h index 1e6eef578..b6459adc4 100644 --- a/rtengine/color.h +++ b/rtengine/color.h @@ -205,6 +205,10 @@ public: return r * 0.2126729 + g * 0.7151521 + b * 0.0721750; } + static float rgbLuminance(float r, float g, float b, const double workingspace[3][3]) + { + return r * workingspace[1][0] + g * workingspace[1][1] + b * workingspace[1][2]; + } /** * @brief Convert red/green/blue to L*a*b diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 731eca086..59130512d 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -692,7 +692,7 @@ void Crop::update(int todo) std::unique_ptr fattalCrop; - if ((todo & M_HDR) && params.fattal.enabled) { + if ((todo & M_HDR) && (params.fattal.enabled || params.dehaze.enabled)) { Imagefloat *f = origCrop; int fw = skips(parent->fw, skip); int fh = skips(parent->fh, skip); @@ -741,6 +741,7 @@ void Crop::update(int todo) } if (need_fattal) { + parent->ipf.dehaze(f); parent->ipf.ToneMapFattal02(f); } diff --git a/rtengine/guidedfilter.cc b/rtengine/guidedfilter.cc index f6b702a73..4e31fa300 100644 --- a/rtengine/guidedfilter.cc +++ b/rtengine/guidedfilter.cc @@ -52,11 +52,39 @@ namespace rtengine { #endif +namespace { + +int calculate_subsampling(int w, int h, int r) +{ + if (r == 1) { + return 1; + } + + if (max(w, h) <= 600) { + return 1; + } + + for (int s = 5; s > 0; --s) { + if (r % s == 0) { + return s; + } + } + + return LIM(r / 2, 2, 4); +} + +} // namespace + + void guidedFilter(const array2D &guide, const array2D &src, array2D &dst, int r, float epsilon, bool multithread, int subsampling) { const int W = src.width(); const int H = src.height(); + if (subsampling <= 0) { + subsampling = calculate_subsampling(W, H, r); + } + enum Op { MUL, DIVEPSILON, ADD, SUB, ADDMUL, SUBMUL }; const auto apply = @@ -107,11 +135,14 @@ void guidedFilter(const array2D &guide, const array2D &src, array2 const array2D &p = src; array2D &q = dst; + AlignedBuffer blur_buf(I.width() * I.height()); const auto f_mean = - [](array2D &d, array2D &s, int rad) -> void + [&](array2D &d, array2D &s, int rad) -> void { rad = LIM(rad, 0, (min(s.width(), s.height()) - 1) / 2 - 1); - boxblur(s, d, rad, rad, s.width(), s.height()); + float **src = s; + float **dst = d; + boxblur(src, dst, blur_buf.data, rad, rad, s.width(), s.height()); }; const auto f_subsample = diff --git a/rtengine/guidedfilter.h b/rtengine/guidedfilter.h index 3f987f80e..6691af251 100644 --- a/rtengine/guidedfilter.h +++ b/rtengine/guidedfilter.h @@ -24,6 +24,6 @@ namespace rtengine { -void guidedFilter(const array2D &guide, const array2D &src, array2D &dst, int r, float epsilon, bool multithread, int subsampling=4); +void guidedFilter(const array2D &guide, const array2D &src, array2D &dst, int r, float epsilon, bool multithread, int subsampling=0); } // namespace rtengine diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 2aaf018fb..e7296c8ba 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -419,12 +419,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) readyphase++; - if ((todo & M_HDR) && params.fattal.enabled) { + if ((todo & M_HDR) && (params.fattal.enabled || params.dehaze.enabled)) { if (fattal_11_dcrop_cache) { delete fattal_11_dcrop_cache; fattal_11_dcrop_cache = nullptr; } + ipf.dehaze(orig_prev); ipf.ToneMapFattal02(orig_prev); if (oprevi != orig_prev) { @@ -1453,7 +1454,8 @@ void ImProcCoordinator::process() || params.raw != nextParams.raw || params.retinex != nextParams.retinex || params.wavelet != nextParams.wavelet - || params.dirpyrequalizer != nextParams.dirpyrequalizer; + || params.dirpyrequalizer != nextParams.dirpyrequalizer + || params.dehaze != nextParams.dehaze; params = nextParams; int change = changeSinceLast; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 066d1e306..99d3aa2e6 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -339,6 +339,7 @@ public: void Badpixelscam(CieImage * ncie, double radius, int thresh, int mode, float chrom, bool hotbad); void BadpixelsLab(LabImage * lab, double radius, int thresh, float chrom); + void dehaze(Imagefloat *rgb); void ToneMapFattal02(Imagefloat *rgb); void localContrast(LabImage *lab); void colorToningLabGrid(LabImage *lab, int xstart, int xend, int ystart, int yend, bool MultiThread); diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc new file mode 100644 index 000000000..fecc73e7d --- /dev/null +++ b/rtengine/ipdehaze.cc @@ -0,0 +1,337 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2018 Alberto Griggio + * + * 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 . + */ + +/* + * Haze removal using the algorithm described in the paper: + * + * Single Image Haze Removal Using Dark Channel Prior + * by He, Sun and Tang + * + * using a guided filter for the "soft matting" of the transmission map + * + */ + +#include "improcfun.h" +#include "guidedfilter.h" +#include "rt_math.h" +#include "rt_algo.h" +#include +#include + +extern Options options; + +namespace rtengine { + +namespace { + +#if 0 +# define DEBUG_DUMP(arr) \ + do { \ + Imagefloat im(arr.width(), arr.height()); \ + const char *out = "/tmp/" #arr ".tif"; \ + for (int y = 0; y < im.getHeight(); ++y) { \ + for (int x = 0; x < im.getWidth(); ++x) { \ + im.r(y, x) = im.g(y, x) = im.b(y, x) = arr[y][x] * 65535.f; \ + } \ + } \ + im.saveTIFF(out, 16); \ + } while (false) +#else +# define DEBUG_DUMP(arr) +#endif + + +int get_dark_channel(const array2D &R, const array2D &G, const array2D &B, array2D &dst, int patchsize, float *ambient, bool clip, bool multithread) +{ + const int W = R.width(); + const int H = R.height(); + + int npatches = 0; + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; y += patchsize) { + int pH = min(y+patchsize, H); + for (int x = 0; x < W; x += patchsize, ++npatches) { + float val = RT_INFINITY_F; + int pW = min(x+patchsize, W); + for (int yy = y; yy < pH; ++yy) { + float yval = RT_INFINITY_F; + for (int xx = x; xx < pW; ++xx) { + float r = R[yy][xx]; + float g = G[yy][xx]; + float b = B[yy][xx]; + if (ambient) { + r /= ambient[0]; + g /= ambient[1]; + b /= ambient[2]; + } + yval = min(yval, r, g, b); + } + val = min(val, yval); + } + if (clip) { + val = LIM01(val); + } + for (int yy = y; yy < pH; ++yy) { + std::fill(dst[yy]+x, dst[yy]+pW, val); + } + } + } + + return npatches; +} + + +float estimate_ambient_light(const array2D &R, const array2D &G, const array2D &B, const array2D &dark, int patchsize, int npatches, float ambient[3]) +{ + const int W = R.width(); + const int H = R.height(); + + const auto get_percentile = + [](std::priority_queue &q, float prcnt) -> float + { + size_t n = LIM(q.size() * prcnt, 1, q.size()); + while (q.size() > n) { + q.pop(); + } + return q.top(); + }; + + float darklim = RT_INFINITY_F; + { + std::priority_queue p; + for (int y = 0; y < H; y += patchsize) { + for (int x = 0; x < W; x += patchsize) { + if (!OOG(dark[y][x], 1.f)) { + p.push(dark[y][x]); + } + } + } + darklim = get_percentile(p, 0.95); + } + + std::vector> patches; + patches.reserve(npatches); + + for (int y = 0; y < H; y += patchsize) { + for (int x = 0; x < W; x += patchsize) { + if (dark[y][x] >= darklim && !OOG(dark[y][x], 1.f)) { + patches.push_back(std::make_pair(x, y)); + } + } + } + + if (options.rtSettings.verbose) { + std::cout << "dehaze: computing ambient light from " << patches.size() + << " patches" << std::endl; + } + + float bright_lim = RT_INFINITY_F; + { + std::priority_queue l; + + for (auto &p : patches) { + 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(R[y][x] + G[y][x] + B[y][x]); + } + } + } + + bright_lim = get_percentile(l, 0.95); + } + + 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); + + 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; + bb += b; + ++n; + } + } + } + } + ambient[0] = rr / n; + ambient[1] = gg / n; + ambient[2] = bb / n; + + // taken from darktable + return darklim > 0 ? -1.125f * std::log(darklim) : std::log(std::numeric_limits::max()) / 2; +} + + +void extract_channels(Imagefloat *img, array2D &r, array2D &g, array2D &b, int radius, float epsilon, bool multithread) +{ + const int W = img->getWidth(); + const int H = img->getHeight(); + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + r[y][x] = img->r(y, x); + g[y][x] = img->g(y, x); + b[y][x] = img->b(y, x); + } + } + + guidedFilter(r, r, r, radius, epsilon, multithread); + guidedFilter(g, g, g, radius, epsilon, multithread); + guidedFilter(b, b, b, radius, epsilon, multithread); +} + + +} // namespace + + +void ImProcFunctions::dehaze(Imagefloat *img) +{ + if (!params->dehaze.enabled) { + return; + } + + img->normalizeFloatTo1(); + + const int W = img->getWidth(); + const int H = img->getHeight(); + float strength = LIM01(float(params->dehaze.strength) / 100.f * 0.9f); + + if (options.rtSettings.verbose) { + std::cout << "dehaze: strength = " << strength << std::endl; + } + + array2D dark(W, H); + + int patchsize = max(int(5 / scale), 2); + int npatches = 0; + float ambient[3]; + array2D &t_tilde = dark; + float max_t = 0.f; + + { + array2D R(W, H); + array2D G(W, H); + array2D B(W, H); + extract_channels(img, R, G, B, patchsize, 1e-1, multiThread); + + patchsize = max(max(W, H) / 600, 2); + npatches = get_dark_channel(R, G, B, dark, patchsize, nullptr, false, multiThread); + DEBUG_DUMP(dark); + + max_t = estimate_ambient_light(R, G, B, dark, patchsize, npatches, ambient); + + if (options.rtSettings.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); + } + + if (min(ambient[0], ambient[1], ambient[2]) < 0.01f) { + if (options.rtSettings.verbose) { + std::cout << "dehaze: no haze detected" << std::endl; + } + img->normalizeFloatTo65535(); + return; // probably no haze at all + } + + DEBUG_DUMP(t_tilde); + +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + dark[y][x] = 1.f - strength * dark[y][x]; + } + } + + const int radius = patchsize * 4; + const float epsilon = 1e-5; + array2D &t = t_tilde; + + { + array2D guideB(W, H, img->b.ptrs, ARRAY2D_BYREFERENCE); + guidedFilter(guideB, t_tilde, t, radius, epsilon, multiThread); + } + + DEBUG_DUMP(t); + + if (options.rtSettings.verbose) { + std::cout << "dehaze: max distance is " << max_t << std::endl; + } + + float depth = -float(params->dehaze.depth) / 100.f; + const float t0 = max(1e-3f, std::exp(depth * max_t)); + const float teps = 1e-3f; +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; 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 mt = max(t[y][x], t0, tl + teps, tu + teps); + if (params->dehaze.showDepthMap) { + img->r(y, x) = img->g(y, x) = img->b(y, x) = 1.f - mt; + } 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]; + + img->r(y, x) = r; + img->g(y, x) = g; + img->b(y, x) = b; + } + } + } + + img->normalizeFloatTo65535(); +} + + +} // namespace rtengine diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index f5e598b75..64440d222 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -2368,6 +2368,30 @@ bool SoftLightParams::operator !=(const SoftLightParams& other) const return !(*this == other); } + +DehazeParams::DehazeParams() : + enabled(false), + strength(50), + showDepthMap(false), + depth(25) +{ +} + +bool DehazeParams::operator ==(const DehazeParams& other) const +{ + return + enabled == other.enabled + && strength == other.strength + && showDepthMap == other.showDepthMap + && depth == other.depth; +} + +bool DehazeParams::operator !=(const DehazeParams& other) const +{ + return !(*this == other); +} + + RAWParams::BayerSensor::BayerSensor() : method(getMethodString(Method::AMAZE)), border(4), @@ -2726,6 +2750,8 @@ void ProcParams::setDefaults() softlight = SoftLightParams(); + dehaze = DehazeParams(); + raw = RAWParams(); metadata = MetaDataParams(); @@ -3036,6 +3062,12 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->defringe.threshold, "Defringing", "Threshold", defringe.threshold, keyFile); saveToKeyfile(!pedited || pedited->defringe.huecurve, "Defringing", "HueCurve", defringe.huecurve, keyFile); +// Dehaze + saveToKeyfile(!pedited || pedited->dehaze.enabled, "Dehaze", "Enabled", dehaze.enabled, keyFile); + saveToKeyfile(!pedited || pedited->dehaze.strength, "Dehaze", "Strength", dehaze.strength, keyFile); + saveToKeyfile(!pedited || pedited->dehaze.showDepthMap, "Dehaze", "ShowDepthMap", dehaze.showDepthMap, keyFile); + saveToKeyfile(!pedited || pedited->dehaze.depth, "Dehaze", "Depth", dehaze.depth, keyFile); + // Directional pyramid denoising saveToKeyfile(!pedited || pedited->dirpyrDenoise.enabled, "Directional Pyramid Denoising", "Enabled", dirpyrDenoise.enabled, keyFile); saveToKeyfile(!pedited || pedited->dirpyrDenoise.enhance, "Directional Pyramid Denoising", "Enhance", dirpyrDenoise.enhance, keyFile); @@ -4619,6 +4651,13 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "SoftLight", "Strength", pedited, softlight.strength, pedited->softlight.strength); } + if (keyFile.has_group("Dehaze")) { + assignFromKeyfile(keyFile, "Dehaze", "Enabled", pedited, dehaze.enabled, pedited->dehaze.enabled); + assignFromKeyfile(keyFile, "Dehaze", "Strength", pedited, dehaze.strength, pedited->dehaze.strength); + assignFromKeyfile(keyFile, "Dehaze", "ShowDepthMap", pedited, dehaze.showDepthMap, pedited->dehaze.showDepthMap); + assignFromKeyfile(keyFile, "Dehaze", "Depth", pedited, dehaze.depth, pedited->dehaze.depth); + } + if (keyFile.has_group("Film Simulation")) { assignFromKeyfile(keyFile, "Film Simulation", "Enabled", pedited, filmSimulation.enabled, pedited->filmSimulation.enabled); assignFromKeyfile(keyFile, "Film Simulation", "ClutFilename", pedited, filmSimulation.clutFilename, pedited->filmSimulation.clutFilename); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 22868a844..87134626b 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1227,6 +1227,19 @@ struct SoftLightParams { }; +struct DehazeParams { + bool enabled; + int strength; + bool showDepthMap; + int depth; + + DehazeParams(); + + bool operator==(const DehazeParams &other) const; + bool operator!=(const DehazeParams &other) const; +}; + + /** * Parameters for RAW demosaicing, common to all sensor type */ @@ -1440,6 +1453,7 @@ public: HSVEqualizerParams hsvequalizer; ///< hsv wavelet parameters FilmSimulationParams filmSimulation; ///< film simulation parameters SoftLightParams softlight; ///< softlight parameters + DehazeParams dehaze; ///< dehaze parameters int rank; ///< Custom image quality ranking int colorlabel; ///< Custom color label bool inTrash; ///< Marks deleted image diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index afd8836a1..d11622d17 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -1200,9 +1200,8 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT ipf.firstAnalysis (baseImg, params, hist16); - if (params.fattal.enabled) { - ipf.ToneMapFattal02(baseImg); - } + ipf.dehaze(baseImg); + ipf.ToneMapFattal02(baseImg); // perform transform if (ipf.needsTransform()) { diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 24301e129..46c189f3d 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -856,9 +856,8 @@ private: ipf.firstAnalysis (baseImg, params, hist16); - if (params.fattal.enabled) { - ipf.ToneMapFattal02(baseImg); - } + ipf.dehaze(baseImg); + ipf.ToneMapFattal02(baseImg); // perform transform (excepted resizing) if (ipf.needsTransform()) { diff --git a/rtengine/tmo_fattal02.cc b/rtengine/tmo_fattal02.cc index dc7826501..124cdbfb1 100644 --- a/rtengine/tmo_fattal02.cc +++ b/rtengine/tmo_fattal02.cc @@ -952,7 +952,7 @@ inline void rescale_nearest (const Array2Df &src, Array2Df &dst, bool multithrea inline float luminance (float r, float g, float b, TMatrix ws) { - return r * ws[1][0] + g * ws[1][1] + b * ws[1][2]; + return Color::rgbLuminance(r, g, b, ws); } @@ -1014,6 +1014,10 @@ inline int find_fast_dim (int dim) void ImProcFunctions::ToneMapFattal02 (Imagefloat *rgb) { + if (!params->fattal.enabled) { + return; + } + BENCHFUN const int detail_level = 3; diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 8150fbce3..434e8187e 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -156,6 +156,7 @@ set(NONCLISOURCEFILES metadatapanel.cc labgrid.cc softlight.cc + dehaze.cc ) include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/rtgui/addsetids.h b/rtgui/addsetids.h index 8cf39aa29..1d9c621eb 100644 --- a/rtgui/addsetids.h +++ b/rtgui/addsetids.h @@ -142,6 +142,7 @@ enum { ADDSET_BAYER_DUALDEMOZCONTRAST, ADDSET_XTRANS_FALSE_COLOR_SUPPRESSION, ADDSET_SOFTLIGHT_STRENGTH, + ADDSET_DEHAZE_STRENGTH, ADDSET_PARAM_NUM // THIS IS USED AS A DELIMITER!! }; diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 0b17d9bd3..80796d0ae 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -202,6 +202,7 @@ void BatchToolPanelCoordinator::initSession () colortoning->setAdjusterBehavior (options.baBehav[ADDSET_COLORTONING_SPLIT], options.baBehav[ADDSET_COLORTONING_SATTHRESHOLD], options.baBehav[ADDSET_COLORTONING_SATOPACITY], options.baBehav[ADDSET_COLORTONING_STRENGTH], options.baBehav[ADDSET_COLORTONING_BALANCE]); filmSimulation->setAdjusterBehavior(options.baBehav[ADDSET_FILMSIMULATION_STRENGTH]); softlight->setAdjusterBehavior(options.baBehav[ADDSET_SOFTLIGHT_STRENGTH]); + dehaze->setAdjusterBehavior(options.baBehav[ADDSET_DEHAZE_STRENGTH]); retinex->setAdjusterBehavior (options.baBehav[ADDSET_RETI_STR], options.baBehav[ADDSET_RETI_NEIGH], options.baBehav[ADDSET_RETI_LIMD], options.baBehav[ADDSET_RETI_OFFS], options.baBehav[ADDSET_RETI_VART], options.baBehav[ADDSET_RETI_GAM], options.baBehav[ADDSET_RETI_SLO]); chmixer->setAdjusterBehavior (options.baBehav[ADDSET_CHMIXER] ); @@ -292,6 +293,7 @@ void BatchToolPanelCoordinator::initSession () if (options.baBehav[ADDSET_COLORTONING_STRENGTH]) { pparams.colorToning.strength = 0; } if (options.baBehav[ADDSET_FILMSIMULATION_STRENGTH]) { pparams.filmSimulation.strength = 0; } if (options.baBehav[ADDSET_SOFTLIGHT_STRENGTH]) { pparams.softlight.strength = 0; } + if (options.baBehav[ADDSET_DEHAZE_STRENGTH]) { pparams.dehaze.strength = 0; } if (options.baBehav[ADDSET_ROTATE_DEGREE]) { pparams.rotate.degree = 0; } if (options.baBehav[ADDSET_RESIZE_SCALE]) { pparams.resize.scale = 0; } if (options.baBehav[ADDSET_DIST_AMOUNT]) { pparams.distortion.amount = 0; } diff --git a/rtgui/dehaze.cc b/rtgui/dehaze.cc new file mode 100644 index 000000000..0f0892ac6 --- /dev/null +++ b/rtgui/dehaze.cc @@ -0,0 +1,151 @@ +/** -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2018 Alberto Griggio + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include "dehaze.h" +#include "eventmapper.h" +#include +#include + +using namespace rtengine; +using namespace rtengine::procparams; + +Dehaze::Dehaze(): FoldableToolPanel(this, "dehaze", M("TP_DEHAZE_LABEL"), false, true) +{ + auto m = ProcEventMapper::getInstance(); + EvDehazeEnabled = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_ENABLED"); + EvDehazeStrength = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_STRENGTH"); + EvDehazeShowDepthMap = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_SHOW_DEPTH_MAP"); + EvDehazeDepth = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_DEPTH"); + + strength = Gtk::manage(new Adjuster(M("TP_DEHAZE_STRENGTH"), 0., 100., 1., 50.)); + strength->setAdjusterListener(this); + strength->show(); + + depth = Gtk::manage(new Adjuster(M("TP_DEHAZE_DEPTH"), 0., 100., 1., 25.)); + depth->setAdjusterListener(this); + depth->show(); + + showDepthMap = Gtk::manage(new Gtk::CheckButton(M("TP_DEHAZE_SHOW_DEPTH_MAP"))); + showDepthMap->signal_toggled().connect(sigc::mem_fun(*this, &Dehaze::showDepthMapChanged)); + showDepthMap->show(); + + pack_start(*strength); + pack_start(*depth); + pack_start(*showDepthMap); +} + + +void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) +{ + disableListener(); + + if (pedited) { + strength->setEditedState(pedited->dehaze.strength ? Edited : UnEdited); + depth->setEditedState(pedited->dehaze.depth ? Edited : UnEdited); + set_inconsistent(multiImage && !pedited->dehaze.enabled); + showDepthMap->set_inconsistent(!pedited->dehaze.showDepthMap); + } + + setEnabled(pp->dehaze.enabled); + strength->setValue(pp->dehaze.strength); + depth->setValue(pp->dehaze.depth); + showDepthMap->set_active(pp->dehaze.showDepthMap); + + enableListener(); +} + + +void Dehaze::write(ProcParams *pp, ParamsEdited *pedited) +{ + pp->dehaze.strength = strength->getValue(); + pp->dehaze.depth = depth->getValue(); + pp->dehaze.enabled = getEnabled(); + pp->dehaze.showDepthMap = showDepthMap->get_active(); + + if (pedited) { + pedited->dehaze.strength = strength->getEditedState(); + pedited->dehaze.depth = depth->getEditedState(); + pedited->dehaze.enabled = !get_inconsistent(); + pedited->dehaze.showDepthMap = !showDepthMap->get_inconsistent(); + } +} + +void Dehaze::setDefaults(const ProcParams *defParams, const ParamsEdited *pedited) +{ + strength->setDefault(defParams->dehaze.strength); + depth->setDefault(defParams->dehaze.depth); + + if (pedited) { + strength->setDefaultEditedState(pedited->dehaze.strength ? Edited : UnEdited); + depth->setDefaultEditedState(pedited->dehaze.depth ? Edited : UnEdited); + } else { + strength->setDefaultEditedState(Irrelevant); + depth->setDefaultEditedState(Irrelevant); + } +} + + +void Dehaze::adjusterChanged(Adjuster* a, double newval) +{ + if (listener && getEnabled()) { + if (a == strength) { + listener->panelChanged(EvDehazeStrength, a->getTextValue()); + } else if (a == depth) { + listener->panelChanged(EvDehazeDepth, a->getTextValue()); + } + } +} + + +void Dehaze::enabledChanged () +{ + if (listener) { + if (get_inconsistent()) { + listener->panelChanged(EvDehazeEnabled, M("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged(EvDehazeEnabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(EvDehazeEnabled, M("GENERAL_DISABLED")); + } + } +} + + +void Dehaze::showDepthMapChanged() +{ + if (listener) { + listener->panelChanged(EvDehazeShowDepthMap, showDepthMap->get_active() ? M("GENERAL_ENABLED") : M("GENERAL_DISABLED")); + } +} + + +void Dehaze::setBatchMode(bool batchMode) +{ + ToolPanel::setBatchMode(batchMode); + + strength->showEditedCB(); + depth->showEditedCB(); +} + + +void Dehaze::setAdjusterBehavior(bool strengthAdd) +{ + strength->setAddMode(strengthAdd); +} + diff --git a/rtgui/dehaze.h b/rtgui/dehaze.h new file mode 100644 index 000000000..0ae7749e4 --- /dev/null +++ b/rtgui/dehaze.h @@ -0,0 +1,53 @@ +/** -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2018 Alberto Griggio + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#include +#include "adjuster.h" +#include "toolpanel.h" + +class Dehaze: public ToolParamBlock, public AdjusterListener, public FoldableToolPanel +{ +private: + Adjuster *strength; + Adjuster *depth; + Gtk::CheckButton *showDepthMap; + + rtengine::ProcEvent EvDehazeEnabled; + rtengine::ProcEvent EvDehazeStrength; + rtengine::ProcEvent EvDehazeDepth; + rtengine::ProcEvent EvDehazeShowDepthMap; + +public: + + Dehaze(); + + void read(const rtengine::procparams::ProcParams *pp, const ParamsEdited *pedited=nullptr); + void write(rtengine::procparams::ProcParams *pp, ParamsEdited *pedited=nullptr); + void setDefaults(const rtengine::procparams::ProcParams *defParams, const ParamsEdited *pedited=nullptr); + void setBatchMode(bool batchMode); + + void adjusterChanged(Adjuster *a, double newval); + void enabledChanged(); + void showDepthMapChanged(); + void setAdjusterBehavior(bool strengthAdd); + void adjusterAutoToggled(Adjuster* a, bool newval) {} +}; + diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 570d6ab2b..167aef401 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -568,6 +568,10 @@ void ParamsEdited::set(bool v) filmSimulation.strength = v; softlight.enabled = v; softlight.strength = v; + dehaze.enabled = v; + dehaze.strength = v; + dehaze.showDepthMap = v; + dehaze.depth = v; metadata.mode = v; exif = v; @@ -1123,6 +1127,10 @@ void ParamsEdited::initFrom(const std::vector& filmSimulation.strength = filmSimulation.strength && p.filmSimulation.strength == other.filmSimulation.strength; softlight.enabled = softlight.enabled && p.softlight.enabled == other.softlight.enabled; softlight.strength = softlight.strength && p.softlight.strength == other.softlight.strength; + dehaze.enabled = dehaze.enabled && p.dehaze.enabled == other.dehaze.enabled; + dehaze.strength = dehaze.strength && p.dehaze.strength == other.dehaze.strength; + dehaze.showDepthMap = dehaze.showDepthMap && p.dehaze.showDepthMap == other.dehaze.showDepthMap; + dehaze.depth = dehaze.depth && p.dehaze.depth == other.dehaze.depth; metadata.mode = metadata.mode && p.metadata.mode == other.metadata.mode; // How the hell can we handle that??? @@ -3124,7 +3132,23 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng if (softlight.strength) { toEdit.softlight.strength = dontforceSet && options.baBehav[ADDSET_SOFTLIGHT_STRENGTH] ? toEdit.softlight.strength + mods.softlight.strength : mods.softlight.strength; } + + if (dehaze.enabled) { + toEdit.dehaze.enabled = mods.dehaze.enabled; + } + + if (dehaze.strength) { + toEdit.dehaze.strength = dontforceSet && options.baBehav[ADDSET_DEHAZE_STRENGTH] ? toEdit.dehaze.strength + mods.dehaze.strength : mods.dehaze.strength; + } + if (dehaze.depth) { + toEdit.dehaze.depth = mods.dehaze.depth; + } + + if (dehaze.showDepthMap) { + toEdit.dehaze.showDepthMap = mods.dehaze.showDepthMap; + } + if (metadata.mode) { toEdit.metadata.mode = mods.metadata.mode; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index aa9c01bd9..75bd54076 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -724,6 +724,16 @@ public: bool strength; }; +class DehazeParamsEdited +{ +public: + bool enabled; + bool strength; + bool showDepthMap; + bool depth; +}; + + class RAWParamsEdited { @@ -867,6 +877,7 @@ public: HSVEqualizerParamsEdited hsvequalizer; FilmSimulationParamsEdited filmSimulation; SoftLightParamsEdited softlight; + DehazeParamsEdited dehaze; MetaDataParamsEdited metadata; bool exif; bool iptc; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index 91e542e85..e0e6a7219 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -65,6 +65,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren dirpyrden = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DIRPYRDENOISE"))); defringe = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DEFRINGE"))); dirpyreq = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DIRPYREQUALIZER"))); + dehaze = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DEHAZE")) ); // Advanced Settings: retinex = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_RETINEX"))); @@ -168,6 +169,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[1]->pack_start (*dirpyrden, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*defringe, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*dirpyreq, Gtk::PACK_SHRINK, 2); + vboxes[1]->pack_start (*dehaze, Gtk::PACK_SHRINK, 2); //COLOR vboxes[2]->pack_start (*color, Gtk::PACK_SHRINK, 2); @@ -327,6 +329,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren dirpyrdenConn = dirpyrden->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); defringeConn = defringe->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); dirpyreqConn = dirpyreq->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); + dehazeConn = dehaze->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); // Advanced Settings: retinexConn = retinex->signal_toggled().connect (sigc::bind (sigc::mem_fun(*advanced, &Gtk::CheckButton::set_inconsistent), true)); @@ -534,6 +537,7 @@ void PartialPasteDlg::detailToggled () ConnectionBlocker dirpyrdenBlocker(dirpyrdenConn); ConnectionBlocker defringeBlocker(defringeConn); ConnectionBlocker dirpyreqBlocker(dirpyreqConn); + ConnectionBlocker dehazeBlocker(dehazeConn); detail->set_inconsistent (false); @@ -545,6 +549,7 @@ void PartialPasteDlg::detailToggled () dirpyrden->set_active (detail->get_active ()); defringe->set_active (detail->get_active ()); dirpyreq->set_active (detail->get_active ()); + dehaze->set_active (detail->get_active ()); } void PartialPasteDlg::advancedToggled () @@ -762,6 +767,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.softlight = falsePE.softlight; } + if (!dehaze->get_active ()) { + filterPE.dehaze = falsePE.dehaze; + } + if (!rgbcurves->get_active ()) { filterPE.rgbCurves = falsePE.rgbCurves; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 5195d0756..f551ac134 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -63,6 +63,7 @@ public: Gtk::CheckButton* dirpyrden; Gtk::CheckButton* defringe; Gtk::CheckButton* dirpyreq; + Gtk::CheckButton* dehaze; // options in wavelet Gtk::CheckButton* wavelet; @@ -131,7 +132,7 @@ public: sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaConn, rawConn, advancedConn; sigc::connection wbConn, exposureConn, localcontrastConn, shConn, pcvignetteConn, gradientConn, labcurveConn, colorappearanceConn; - sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn; + sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn, dehazeConn; sigc::connection vibranceConn, chmixerConn, hsveqConn, rgbcurvesConn, chmixerbwConn, colortoningConn, filmSimulationConn, softlightConn; sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, perspectiveConn, commonTransConn; diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index 1f59a91cd..b15e10e49 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -255,6 +255,10 @@ Gtk::Widget* Preferences::getBatchProcPanel () appendBehavList (mi, M ("TP_DIRPYRDENOISE_MAIN_GAMMA"), ADDSET_DIRPYRDN_GAMMA, true); appendBehavList (mi, M ("TP_DIRPYRDENOISE_MEDIAN_PASSES"), ADDSET_DIRPYRDN_PASSES, true); + mi = behModel->append (); + mi->set_value ( behavColumns.label, M ("TP_DEHAZE_LABEL") ); + appendBehavList ( mi, M ( "TP_DEHAZE_STRENGTH" ), ADDSET_DEHAZE_STRENGTH, true ); + mi = behModel->append (); mi->set_value (behavColumns.label, M ("TP_WBALANCE_LABEL")); appendBehavList (mi, M ("TP_WBALANCE_TEMPERATURE"), ADDSET_WB_TEMPERATURE, true); diff --git a/rtgui/thumbnail.cc b/rtgui/thumbnail.cc index c2d23eab2..4bf8736b4 100644 --- a/rtgui/thumbnail.cc +++ b/rtgui/thumbnail.cc @@ -448,7 +448,8 @@ void Thumbnail::setProcParams (const ProcParams& pp, ParamsEdited* pe, int whoCh || pparams.icm != pp.icm || pparams.hsvequalizer != pp.hsvequalizer || pparams.filmSimulation != pp.filmSimulation - || pparams.softlight != pp.softlight; + || pparams.softlight != pp.softlight + || pparams.dehaze != pp.dehaze; { MyMutex::MyLock lock(mutex); diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 43aaeecd2..bf155b1d6 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -77,6 +77,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), hasChan hsvequalizer = Gtk::manage (new HSVEqualizer ()); filmSimulation = Gtk::manage (new FilmSimulation ()); softlight = Gtk::manage(new SoftLight()); + dehaze = Gtk::manage(new Dehaze()); sensorbayer = Gtk::manage (new SensorBayer ()); sensorxtrans = Gtk::manage (new SensorXTrans ()); bayerprocess = Gtk::manage (new BayerProcess ()); @@ -126,6 +127,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), hasChan addPanel (detailsPanel, dirpyrdenoise); addPanel (detailsPanel, defringe); addPanel (detailsPanel, dirpyrequalizer); + addPanel (detailsPanel, dehaze); addPanel (advancedPanel, wavelet); addPanel (transformPanel, crop); addPanel (transformPanel, resize); diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index b5a23f690..041386771 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -80,6 +80,7 @@ #include "fattaltonemap.h" #include "localcontrast.h" #include "softlight.h" +#include "dehaze.h" #include "guiutils.h" class ImageEditorCoordinator; @@ -135,6 +136,7 @@ protected: DirPyrEqualizer* dirpyrequalizer; HSVEqualizer* hsvequalizer; SoftLight *softlight; + Dehaze *dehaze; FilmSimulation *filmSimulation; SensorBayer * sensorbayer; SensorXTrans * sensorxtrans;