From 14ac4babec51e1c4e2cb2e3d09542e5afc5d583c Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 10 Oct 2018 10:02:06 +0200 Subject: [PATCH 01/12] added haze removal tool Based on 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 --- rtdata/languages/default | 35 ++-- rtengine/CMakeLists.txt | 1 + rtengine/color.h | 4 + rtengine/dcrop.cc | 3 +- rtengine/improccoordinator.cc | 3 +- rtengine/improcfun.h | 1 + rtengine/ipdehaze.cc | 335 ++++++++++++++++++++++++++++++++++ rtengine/procparams.cc | 31 ++++ rtengine/procparams.h | 12 ++ rtengine/rtthumbnail.cc | 1 + rtengine/simpleprocess.cc | 1 + rtengine/tmo_fattal02.cc | 2 +- rtgui/CMakeLists.txt | 1 + rtgui/addsetids.h | 1 + rtgui/batchtoolpanelcoord.cc | 2 + rtgui/dehaze.cc | 115 ++++++++++++ rtgui/dehaze.h | 47 +++++ rtgui/paramsedited.cc | 12 ++ rtgui/paramsedited.h | 9 + rtgui/partialpastedlg.cc | 9 + rtgui/partialpastedlg.h | 3 +- rtgui/preferences.cc | 4 + rtgui/toolpanelcoord.cc | 2 + rtgui/toolpanelcoord.h | 2 + 24 files changed, 617 insertions(+), 19 deletions(-) create mode 100644 rtengine/ipdehaze.cc create mode 100644 rtgui/dehaze.cc create mode 100644 rtgui/dehaze.h diff --git a/rtdata/languages/default b/rtdata/languages/default index 963659884..dcf626948 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -398,7 +398,7 @@ HISTORY_MSG_145;Microcontrast - Uniformity HISTORY_MSG_146;Edge sharpening HISTORY_MSG_147;ES - Luminance only HISTORY_MSG_148;Microcontrast -HISTORY_MSG_149;Microcontrast - 3×3 matrix +HISTORY_MSG_149;Microcontrast - 33 matrix HISTORY_MSG_150;Post-demosaic artifact/noise red. HISTORY_MSG_151;Vibrance HISTORY_MSG_152;Vib - Pastel tones @@ -728,6 +728,8 @@ 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_ENABLED;Haze Removal +HISTORY_MSG_DEHAZE_STRENGTH;Dehaze - Strength HISTORY_MSG_DUALDEMOSAIC_CONTRAST;AMaZE+VNG4 - Contrast threshold HISTORY_MSG_HISTMATCHING;Auto-matched tone curve HISTORY_MSG_ICM_OUTPUT_PRIMARIES;Output - Primaries @@ -807,7 +809,7 @@ IPTCPANEL_CITY;City IPTCPANEL_CITYHINT;Enter the name of the city pictured in this image. IPTCPANEL_COPYHINT;Copy IPTC settings to clipboard. IPTCPANEL_COPYRIGHT;Copyright notice -IPTCPANEL_COPYRIGHTHINT;Enter a Notice on the current owner of the Copyright for this image, such as ©2008 Jane Doe. +IPTCPANEL_COPYRIGHTHINT;Enter a Notice on the current owner of the Copyright for this image, such as 2008 Jane Doe. IPTCPANEL_COUNTRY;Country IPTCPANEL_COUNTRYHINT;Enter the name of the country pictured in this image. IPTCPANEL_CREATOR;Creator @@ -954,6 +956,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 @@ -1366,9 +1369,9 @@ TP_COARSETRAF_TOOLTIP_ROTLEFT;Rotate left.\n\nShortcuts:\n[ - Multiple Ed TP_COARSETRAF_TOOLTIP_ROTRIGHT;Rotate right.\n\nShortcuts:\n] - Multiple Editor Tabs Mode,\nAlt-] - Single Editor Tab Mode. TP_COARSETRAF_TOOLTIP_VFLIP;Flip vertically. TP_COLORAPP_ADAPTSCENE;Scene absolute luminance -TP_COLORAPP_ADAPTSCENE_TOOLTIP;Absolute luminance of the scene environment (cd/m²).\n1) Calculated from the Exif data:\nShutter speed - ISO speed - F number - camera exposure correction.\n2) Calculated from the raw white point and RT's Exposure Compensation slider. -TP_COLORAPP_ADAPTVIEWING;Viewing absolute luminance (cd/m²) -TP_COLORAPP_ADAPTVIEWING_TOOLTIP;Absolute luminance of the viewing environment\n(usually 16cd/m²). +TP_COLORAPP_ADAPTSCENE_TOOLTIP;Absolute luminance of the scene environment (cd/m).\n1) Calculated from the Exif data:\nShutter speed - ISO speed - F number - camera exposure correction.\n2) Calculated from the raw white point and RT's Exposure Compensation slider. +TP_COLORAPP_ADAPTVIEWING;Viewing absolute luminance (cd/m) +TP_COLORAPP_ADAPTVIEWING_TOOLTIP;Absolute luminance of the viewing environment\n(usually 16cd/m). TP_COLORAPP_ADAP_AUTO_TOOLTIP;If the checkbox is checked (recommended) RawTherapee calculates an optimum value from the Exif data.\nTo set the value manually, uncheck the checkbox first. TP_COLORAPP_ALGO;Algorithm TP_COLORAPP_ALGO_ALL;All @@ -1405,7 +1408,7 @@ TP_COLORAPP_FREE;Free temp+green + CAT02 + [output] TP_COLORAPP_GAMUT;Gamut control (L*a*b*) TP_COLORAPP_GAMUT_TOOLTIP;Allow gamut control in L*a*b* mode. TP_COLORAPP_HUE;Hue (h) -TP_COLORAPP_HUE_TOOLTIP;Hue (h) - angle between 0° and 360°. +TP_COLORAPP_HUE_TOOLTIP;Hue (h) - angle between 0 and 360. TP_COLORAPP_LABEL;CIE Color Appearance Model 2002 TP_COLORAPP_LABEL_CAM02;Image Adjustments TP_COLORAPP_LABEL_SCENE;Scene Conditions @@ -1505,6 +1508,8 @@ TP_DARKFRAME_LABEL;Dark-Frame TP_DEFRINGE_LABEL;Defringe TP_DEFRINGE_RADIUS;Radius TP_DEFRINGE_THRESHOLD;Threshold +TP_DEHAZE_LABEL;Haze Removal +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 ! @@ -1552,16 +1557,16 @@ TP_DIRPYRDENOISE_MEDIAN_METHOD_RGB;RGB TP_DIRPYRDENOISE_MEDIAN_METHOD_TOOLTIP;When using the "Luminance only" and "L*a*b*" methods, median filtering will be performed just after the wavelet step in the noise reduction pipeline.\nWhen using the "RGB" mode, it will be performed at the very end of the noise reduction pipeline. TP_DIRPYRDENOISE_MEDIAN_METHOD_WEIGHTED;Weighted L* (little) + a*b* (normal) TP_DIRPYRDENOISE_MEDIAN_PASSES;Median iterations -TP_DIRPYRDENOISE_MEDIAN_PASSES_TOOLTIP;Applying three median filter iterations with a 3×3 window size often leads to better results than using one median filter iteration with a 7×7 window size. +TP_DIRPYRDENOISE_MEDIAN_PASSES_TOOLTIP;Applying three median filter iterations with a 33 window size often leads to better results than using one median filter iteration with a 77 window size. TP_DIRPYRDENOISE_MEDIAN_TYPE;Median type -TP_DIRPYRDENOISE_MEDIAN_TYPE_TOOLTIP;Apply a median filter of the desired window size. The larger the window's size, the longer it takes.\n\n3×3 soft: treats 5 pixels in a 3×3 pixel window.\n3×3: treats 9 pixels in a 3×3 pixel window.\n5×5 soft: treats 13 pixels in a 5×5 pixel window.\n5×5: treats 25 pixels in a 5×5 pixel window.\n7×7: treats 49 pixels in a 7×7 pixel window.\n9×9: treats 81 pixels in a 9×9 pixel window.\n\nSometimes it is possible to achieve higher quality running several iterations with a smaller window size than one iteration with a larger one. +TP_DIRPYRDENOISE_MEDIAN_TYPE_TOOLTIP;Apply a median filter of the desired window size. The larger the window's size, the longer it takes.\n\n33 soft: treats 5 pixels in a 33 pixel window.\n33: treats 9 pixels in a 33 pixel window.\n55 soft: treats 13 pixels in a 55 pixel window.\n55: treats 25 pixels in a 55 pixel window.\n77: treats 49 pixels in a 77 pixel window.\n99: treats 81 pixels in a 99 pixel window.\n\nSometimes it is possible to achieve higher quality running several iterations with a smaller window size than one iteration with a larger one. TP_DIRPYRDENOISE_SLI;Slider -TP_DIRPYRDENOISE_TYPE_3X3;3×3 -TP_DIRPYRDENOISE_TYPE_3X3SOFT;3×3 soft -TP_DIRPYRDENOISE_TYPE_5X5;5×5 -TP_DIRPYRDENOISE_TYPE_5X5SOFT;5×5 soft -TP_DIRPYRDENOISE_TYPE_7X7;7×7 -TP_DIRPYRDENOISE_TYPE_9X9;9×9 +TP_DIRPYRDENOISE_TYPE_3X3;33 +TP_DIRPYRDENOISE_TYPE_3X3SOFT;33 soft +TP_DIRPYRDENOISE_TYPE_5X5;55 +TP_DIRPYRDENOISE_TYPE_5X5SOFT;55 soft +TP_DIRPYRDENOISE_TYPE_7X7;77 +TP_DIRPYRDENOISE_TYPE_9X9;99 TP_DIRPYREQUALIZER_ALGO;Skin Color Range TP_DIRPYREQUALIZER_ALGO_TOOLTIP;Fine: closer to the colors of the skin, minimizing the action on other colors\nLarge: avoid more artifacts. TP_DIRPYREQUALIZER_ARTIF;Reduce artifacts @@ -2005,7 +2010,7 @@ TP_SHARPENING_USM;Unsharp Mask TP_SHARPENMICRO_AMOUNT;Quantity TP_SHARPENMICRO_CONTRAST;Contrast threshold TP_SHARPENMICRO_LABEL;Microcontrast -TP_SHARPENMICRO_MATRIX;3×3 matrix instead of 5×5 +TP_SHARPENMICRO_MATRIX;33 matrix instead of 55 TP_SHARPENMICRO_UNIFORMITY;Uniformity TP_SOFTLIGHT_LABEL;Soft Light TP_SOFTLIGHT_STRENGTH;Strength 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 002747070..d72b7389c 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/improccoordinator.cc b/rtengine/improccoordinator.cc index 298c3fc58..dec7fef91 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -413,12 +413,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, Crop* cropCall) 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) { diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index a97ecef40..216641a45 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..aed647417 --- /dev/null +++ b/rtengine/ipdehaze.cc @@ -0,0 +1,335 @@ +/* -*- 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 Imagefloat &src, array2D &dst, + int patchsize, float *ambient, bool multithread) +{ + const int w = src.getWidth(); + const int h = src.getHeight(); + + int npatches = 0; + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < src.getHeight(); y += patchsize) { + int pH = std::min(y+patchsize, h); + for (int x = 0; x < src.getWidth(); x += patchsize, ++npatches) { + float val = RT_INFINITY_F; + int pW = std::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 = src.r(yy, xx); + float g = src.g(yy, xx); + float b = src.b(yy, xx); + if (ambient) { + r /= ambient[0]; + g /= ambient[1]; + b /= ambient[2]; + } + yval = min(yval, r, g, b); + } + val = min(val, yval); + } + for (int yy = y; yy < pH; ++yy) { + std::fill(dst[yy]+x, dst[yy]+pW, val); + } + for (int yy = y; yy < pH; ++yy) { + for (int xx = x; xx < pW; ++xx) { + float r = src.r(yy, xx); + float g = src.g(yy, xx); + float b = src.b(yy, xx); + if (ambient) { + r /= ambient[0]; + g /= ambient[1]; + b /= ambient[2]; + } + float l = min(r, g, b); + if (l >= 2.f * val) { + dst[yy][xx] = l; + } + } + } + } + } + + return npatches; +} + + +int estimate_ambient_light(const Imagefloat *img, const array2D &dark, const array2D &Y, int patchsize, int npatches, float ambient[3]) +{ + const int W = img->getWidth(); + const int H = img->getHeight(); + + 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 lim = RT_INFINITY_F; + { + std::priority_queue p; + for (int y = 0; y < H; y += patchsize) { + for (int x = 0; x < W; x += patchsize) { + p.push(dark[y][x]); + } + } + lim = 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] >= lim) { + patches.push_back(std::make_pair(x, y)); + } + } + } + + if (options.rtSettings.verbose) { + std::cout << "dehaze: computing ambient light from " << patches.size() + << " patches" << std::endl; + } + + { + std::priority_queue l; + + for (auto &p : patches) { + const int pW = std::min(p.first+patchsize, W); + const int pH = std::min(p.second+patchsize, H); + + for (int y = p.second; y < pH; ++y) { + for (int x = p.first; x < pW; ++x) { + l.push(Y[y][x]); + } + } + } + + lim = get_percentile(l, 0.95); + } + + double rr = 0, gg = 0, bb = 0; + int n = 0; + for (auto &p : patches) { + const int pW = std::min(p.first+patchsize, W); + const int pH = std::min(p.second+patchsize, H); + + for (int y = p.second; y < pH; ++y) { + for (int x = p.first; x < pW; ++x) { + if (Y[y][x] >= lim) { + float r = img->r(y, x); + float g = img->g(y, x); + float b = img->b(y, x); + rr += r; + gg += g; + bb += b; + ++n; + } + } + } + } + ambient[0] = rr / n; + ambient[1] = gg / n; + ambient[2] = bb / n; + + return n; +} + + +void get_luminance(Imagefloat *img, array2D &Y, TMatrix ws, 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) { + Y[y][x] = Color::rgbLuminance(img->r(y, x), img->g(y, x), img->b(y, x), ws); + } + } +} + + +} // namespace + + +void ImProcFunctions::dehaze(Imagefloat *img) +{ + if (!params->dehaze.enabled) { + return; + } + + img->normalizeFloatTo1(); + + const int W = img->getWidth(); + const int H = img->getHeight(); + const 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); + const int patchsize = std::max(W / 200, 2); + int npatches = get_dark_channel(*img, dark, patchsize, nullptr, multiThread); + DEBUG_DUMP(dark); + + TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); + array2D Y(W, H); + get_luminance(img, Y, ws, multiThread); + + float ambient[3]; + int n = estimate_ambient_light(img, dark, Y, patchsize, npatches, ambient); + float ambient_Y = Color::rgbLuminance(ambient[0], ambient[1], ambient[2], ws); + + if (options.rtSettings.verbose) { + std::cout << "dehaze: ambient light is " + << ambient[0] << ", " << ambient[1] << ", " << ambient[2] + << " (average of " << n << ")" + << std::endl; + std::cout << " ambient luminance is " << ambient_Y << std::endl; + } + + 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 + } + + array2D &t_tilde = dark; + get_dark_channel(*img, dark, patchsize, ambient, multiThread); + 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 * 2; + const float epsilon = 2.5e-4; + array2D &t = t_tilde; + + guidedFilter(Y, t_tilde, t, radius, epsilon, multiThread); + + DEBUG_DUMP(t); + + const float t0 = 0.01; +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float mt = std::max(t[y][x], t0); + float r = (img->r(y, x) - ambient[0]) / mt + ambient[0]; + float g = (img->g(y, x) - ambient[1]) / mt + ambient[1]; + float b = (img->b(y, x) - ambient[2]) / mt + ambient[2]; + img->r(y, x) = r; + img->g(y, x) = g; + img->b(y, x) = b; + } + } + + float oldmed; + findMinMaxPercentile(Y, Y.width() * Y.height(), 0.5, oldmed, 0.5, oldmed, multiThread); + + get_luminance(img, Y, ws, multiThread); + float newmed; + + findMinMaxPercentile(Y, Y.width() * Y.height(), 0.5, newmed, 0.5, newmed, multiThread); + + if (newmed > 1e-5f) { + const float f1 = oldmed / newmed; + const float f = f1 * 65535.f; +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float r = img->r(y, x); + float g = img->g(y, x); + float b = img->b(y, x); + float h, s, l; + Color::rgb2hslfloat(r * f, g * f, b * f, h, s, l); + s = LIM01(s / f1); + Color::hsl2rgbfloat(h, s, l, img->r(y, x), img->g(y, x), img->b(y, x)); + } + } + } +} + + +} // namespace rtengine diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 4d0c7aed0..ceace7c3b 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -2373,6 +2373,26 @@ bool SoftLightParams::operator !=(const SoftLightParams& other) const return !(*this == other); } + +DehazeParams::DehazeParams() : + enabled(false), + strength(50) +{ +} + +bool DehazeParams::operator ==(const DehazeParams& other) const +{ + return + enabled == other.enabled + && strength == other.strength; +} + +bool DehazeParams::operator !=(const DehazeParams& other) const +{ + return !(*this == other); +} + + RAWParams::BayerSensor::BayerSensor() : method(getMethodString(Method::AMAZE)), border(4), @@ -2727,6 +2747,8 @@ void ProcParams::setDefaults() softlight = SoftLightParams(); + dehaze = DehazeParams(); + raw = RAWParams(); metadata = MetaDataParams(); @@ -3037,6 +3059,10 @@ 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); + // 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); @@ -4618,6 +4644,11 @@ 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); + } + 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 700c6271c..19fe0a376 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1227,6 +1227,17 @@ struct SoftLightParams { }; +struct DehazeParams { + bool enabled; + int strength; + + DehazeParams(); + + bool operator==(const DehazeParams &other) const; + bool operator!=(const DehazeParams &other) const; +}; + + /** * Parameters for RAW demosaicing, common to all sensor type */ @@ -1438,6 +1449,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 79dee36bf..bb710ee83 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -1200,6 +1200,7 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT ipf.firstAnalysis (baseImg, params, hist16); + ipf.dehaze(baseImg); if (params.fattal.enabled) { ipf.ToneMapFattal02(baseImg); } diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index fbee20f44..0846eacbd 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -854,6 +854,7 @@ private: ipf.firstAnalysis (baseImg, params, hist16); + ipf.dehaze(baseImg); if (params.fattal.enabled) { ipf.ToneMapFattal02(baseImg); } diff --git a/rtengine/tmo_fattal02.cc b/rtengine/tmo_fattal02.cc index dc7826501..351d60bc9 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); } 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 b31f41e4f..13b1f6677 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..6f4814e55 --- /dev/null +++ b/rtgui/dehaze.cc @@ -0,0 +1,115 @@ +/** -*- 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"); + + strength = Gtk::manage(new Adjuster(M("TP_DEHAZE_STRENGTH"), 0., 100., 1., 50.)); + strength->setAdjusterListener(this); + strength->show(); + + pack_start(*strength); +} + + +void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) +{ + disableListener(); + + if (pedited) { + strength->setEditedState(pedited->dehaze.strength ? Edited : UnEdited); + set_inconsistent(multiImage && !pedited->dehaze.enabled); + } + + setEnabled(pp->dehaze.enabled); + strength->setValue(pp->dehaze.strength); + + enableListener(); +} + + +void Dehaze::write(ProcParams *pp, ParamsEdited *pedited) +{ + pp->dehaze.strength = strength->getValue(); + pp->dehaze.enabled = getEnabled(); + + if (pedited) { + pedited->dehaze.strength = strength->getEditedState(); + pedited->dehaze.enabled = !get_inconsistent(); + } +} + +void Dehaze::setDefaults(const ProcParams *defParams, const ParamsEdited *pedited) +{ + strength->setDefault(defParams->dehaze.strength); + + if (pedited) { + strength->setDefaultEditedState(pedited->dehaze.strength ? Edited : UnEdited); + } else { + strength->setDefaultEditedState(Irrelevant); + } +} + + +void Dehaze::adjusterChanged(Adjuster* a, double newval) +{ + if (listener && getEnabled()) { + listener->panelChanged(EvDehazeStrength, 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::setBatchMode(bool batchMode) +{ + ToolPanel::setBatchMode(batchMode); + + strength->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..3617f13ea --- /dev/null +++ b/rtgui/dehaze.h @@ -0,0 +1,47 @@ +/** -*- 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; + + rtengine::ProcEvent EvDehazeEnabled; + rtengine::ProcEvent EvDehazeStrength; + +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 setAdjusterBehavior(bool strengthAdd); +}; + diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 342d9adf9..fef8da8e6 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -566,6 +566,8 @@ void ParamsEdited::set(bool v) filmSimulation.strength = v; softlight.enabled = v; softlight.strength = v; + dehaze.enabled = v; + dehaze.strength = v; metadata.mode = v; exif = v; @@ -1119,6 +1121,8 @@ 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; metadata.mode = metadata.mode && p.metadata.mode == other.metadata.mode; // How the hell can we handle that??? @@ -3112,6 +3116,14 @@ 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 (metadata.mode) { toEdit.metadata.mode = mods.metadata.mode; diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 177b05d85..6a2076032 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -724,6 +724,14 @@ public: bool strength; }; +class DehazeParamsEdited +{ +public: + bool enabled; + bool strength; +}; + + class RAWParamsEdited { @@ -865,6 +873,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 6b192dba9..065deef6a 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 a7b45c902..33d8489f7 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/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index deecb7682..99367999c 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 1c46ee54e..ca5c5fac4 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; @@ -136,6 +137,7 @@ protected: DirPyrEqualizer* dirpyrequalizer; HSVEqualizer* hsvequalizer; SoftLight *softlight; + Dehaze *dehaze; FilmSimulation *filmSimulation; SensorBayer * sensorbayer; SensorXTrans * sensorxtrans; From 75964dfd8b767ded7b92ea47f6f57159518e1be4 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 10 Oct 2018 17:08:36 +0200 Subject: [PATCH 02/12] make sure that dehaze doesn't trigger Fattal tone mapping (bad interaction due to sloppy rebasing) --- rtengine/rtthumbnail.cc | 4 +--- rtengine/simpleprocess.cc | 4 +--- rtengine/tmo_fattal02.cc | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index bb710ee83..8a8ce1d9b 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -1201,9 +1201,7 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT ipf.firstAnalysis (baseImg, params, hist16); ipf.dehaze(baseImg); - if (params.fattal.enabled) { - ipf.ToneMapFattal02(baseImg); - } + ipf.ToneMapFattal02(baseImg); // perform transform if (ipf.needsTransform()) { diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 0846eacbd..5cd630315 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -855,9 +855,7 @@ private: ipf.firstAnalysis (baseImg, params, hist16); ipf.dehaze(baseImg); - if (params.fattal.enabled) { - ipf.ToneMapFattal02(baseImg); - } + ipf.ToneMapFattal02(baseImg); // perform transform (excepted resizing) if (ipf.needsTransform()) { diff --git a/rtengine/tmo_fattal02.cc b/rtengine/tmo_fattal02.cc index 351d60bc9..124cdbfb1 100644 --- a/rtengine/tmo_fattal02.cc +++ b/rtengine/tmo_fattal02.cc @@ -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; From 34321c70125ae89b8874b592e59faba4b9587709 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 10 Oct 2018 22:49:12 +0200 Subject: [PATCH 03/12] guided filter: properly validate radius parameter before calling boxblur --- rtengine/boxblur.h | 2 -- rtengine/guidedfilter.cc | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rtengine/boxblur.h b/rtengine/boxblur.h index f38c8f6e4..805575b77 100644 --- a/rtengine/boxblur.h +++ b/rtengine/boxblur.h @@ -35,8 +35,6 @@ namespace rtengine template void boxblur (T** src, A** dst, int radx, int rady, int W, int H) { //box blur image; box range = (radx,rady) - radx = min(radx, W-1); - rady = min(rady, H-1); AlignedBuffer* buffer = new AlignedBuffer (W * H); float* temp = buffer->data; diff --git a/rtengine/guidedfilter.cc b/rtengine/guidedfilter.cc index 4f4c5a247..3000e1d5d 100644 --- a/rtengine/guidedfilter.cc +++ b/rtengine/guidedfilter.cc @@ -110,6 +110,7 @@ void guidedFilter(const array2D &guide, const array2D &src, array2 const auto f_mean = [](array2D &d, array2D &s, int rad) -> void { + rad = min(rad, s.width() / 2, s.height() / 2); boxblur(s, d, rad, rad, s.width(), s.height()); }; From 83f5205006dc1ba8f893691780aede843795a85f Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 11 Oct 2018 13:41:41 +0200 Subject: [PATCH 04/12] guided filter: proper bounding of the radius before calling boxblur --- rtengine/boxblur.h | 2 ++ rtengine/guidedfilter.cc | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rtengine/boxblur.h b/rtengine/boxblur.h index 805575b77..71452ceae 100644 --- a/rtengine/boxblur.h +++ b/rtengine/boxblur.h @@ -35,6 +35,8 @@ namespace rtengine template void boxblur (T** src, A** dst, int radx, int rady, int W, int H) { //box blur image; box range = (radx,rady) + assert(2*radx+1 < W); + assert(2*rady+1 < H); AlignedBuffer* buffer = new AlignedBuffer (W * H); float* temp = buffer->data; diff --git a/rtengine/guidedfilter.cc b/rtengine/guidedfilter.cc index 3000e1d5d..f6b702a73 100644 --- a/rtengine/guidedfilter.cc +++ b/rtengine/guidedfilter.cc @@ -110,7 +110,7 @@ void guidedFilter(const array2D &guide, const array2D &src, array2 const auto f_mean = [](array2D &d, array2D &s, int rad) -> void { - rad = min(rad, s.width() / 2, s.height() / 2); + rad = LIM(rad, 0, (min(s.width(), s.height()) - 1) / 2 - 1); boxblur(s, d, rad, rad, s.width(), s.height()); }; From 74ae459bf2c8782ced1ab592e4a2e5416ecf1f04 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 11 Oct 2018 13:43:45 +0200 Subject: [PATCH 05/12] dehaze: added more user-controllable parameters --- rtdata/languages/default | 6 +++ rtengine/ipdehaze.cc | 95 ++++++++++++++++++++++++++++++++++++---- rtengine/procparams.cc | 16 ++++++- rtengine/procparams.h | 3 ++ rtgui/dehaze.cc | 54 ++++++++++++++++++++++- rtgui/dehaze.h | 7 +++ rtgui/paramsedited.cc | 18 ++++++++ rtgui/paramsedited.h | 3 ++ 8 files changed, 190 insertions(+), 12 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index dcf626948..facf89498 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -728,7 +728,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_DETAIL;Dehaze - Detail 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;AMaZE+VNG4 - Contrast threshold HISTORY_MSG_HISTMATCHING;Auto-matched tone curve @@ -1508,7 +1511,10 @@ TP_DARKFRAME_LABEL;Dark-Frame TP_DEFRINGE_LABEL;Defringe TP_DEFRINGE_RADIUS;Radius TP_DEFRINGE_THRESHOLD;Threshold +TP_DEHAZE_DEPTH;Depth +TP_DEHAZE_DETAIL;Detail 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 diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index aed647417..7fa1988ce 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -218,6 +218,59 @@ void get_luminance(Imagefloat *img, array2D &Y, TMatrix ws, bool multithr } +void apply_contrast(array2D &dark, int contrast, double scale, bool multithread) +{ + if (contrast) { + const int W = dark.width(); + const int H = dark.height(); + + double tot = 0.0; +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + for (int y = 0; y < H; ++y) { + double ytot = 0.0; + for (int x = 0; x < W; ++x) { + ytot += dark[y][x]; + } +#ifdef _OPENMP + #pragma omp critical +#endif + { + tot += ytot; + } + } + + float avg = tot / (W * H); + + std::vector pts = { + DCT_NURBS, + 0, //black point. Value in [0 ; 1] range + 0, //black point. Value in [0 ; 1] range + + avg - avg * (0.6 - contrast / 250.0), //toe point + avg - avg * (0.6 + contrast / 250.0), //value at toe point + + avg + (1 - avg) * (0.6 - contrast / 250.0), //shoulder point + avg + (1 - avg) * (0.6 + contrast / 250.0), //value at shoulder point + + 1., // white point + 1. // value at white point + }; + + const DiagonalCurve curve(pts, CURVES_MIN_POLY_POINTS / scale); + +#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] = curve.getVal(dark[y][x]); + } + } + } +} + } // namespace @@ -231,14 +284,14 @@ void ImProcFunctions::dehaze(Imagefloat *img) const int W = img->getWidth(); const int H = img->getHeight(); - const float strength = LIM01(float(params->dehaze.strength) / 100.f * 0.9f); + 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); - const int patchsize = std::max(W / 200, 2); + const int patchsize = std::max(W / (200 + max(params->dehaze.detail, 0)), 2); int npatches = get_dark_channel(*img, dark, patchsize, nullptr, multiThread); DEBUG_DUMP(dark); @@ -268,18 +321,27 @@ void ImProcFunctions::dehaze(Imagefloat *img) array2D &t_tilde = dark; get_dark_channel(*img, dark, patchsize, ambient, multiThread); + apply_contrast(dark, params->dehaze.depth, scale, multiThread); DEBUG_DUMP(t_tilde); - + + if (!params->dehaze.showDepthMap) { #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) { - dark[y][x] = 1.f - strength * dark[y][x]; + 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 * 2; + float mult = 2.f; + if (params->dehaze.detail > 0) { + mult -= (params->dehaze.detail / 100.f) * 1.9f; + } else { + mult -= params->dehaze.detail / 10.f; + } + const int radius = max(int(patchsize * mult), 1); const float epsilon = 2.5e-4; array2D &t = t_tilde; @@ -287,6 +349,19 @@ void ImProcFunctions::dehaze(Imagefloat *img) DEBUG_DUMP(t); + + if (params->dehaze.showDepthMap) { +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + img->r(y, x) = img->g(y, x) = img->b(y, x) = t[y][x] * 65535.f; + } + } + return; + } + const float t0 = 0.01; #ifdef _OPENMP #pragma omp parallel for if (multiThread) @@ -313,7 +388,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) if (newmed > 1e-5f) { const float f1 = oldmed / newmed; - const float f = f1 * 65535.f; + const float f = /* f1 * */ 65535.f; #ifdef _OPENMP #pragma omp parallel for if (multiThread) #endif @@ -328,6 +403,8 @@ void ImProcFunctions::dehaze(Imagefloat *img) Color::hsl2rgbfloat(h, s, l, img->r(y, x), img->g(y, x), img->b(y, x)); } } + } else { + img->normalizeFloatTo65535(); } } diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index ceace7c3b..17afb3371 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -2376,7 +2376,10 @@ bool SoftLightParams::operator !=(const SoftLightParams& other) const DehazeParams::DehazeParams() : enabled(false), - strength(50) + strength(50), + showDepthMap(false), + depth(0), + detail(0) { } @@ -2384,7 +2387,10 @@ bool DehazeParams::operator ==(const DehazeParams& other) const { return enabled == other.enabled - && strength == other.strength; + && strength == other.strength + && showDepthMap == other.showDepthMap + && depth == other.depth + && detail == other.detail; } bool DehazeParams::operator !=(const DehazeParams& other) const @@ -3062,6 +3068,9 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo // 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); + saveToKeyfile(!pedited || pedited->dehaze.detail, "Dehaze", "Detail", dehaze.detail, keyFile); // Directional pyramid denoising saveToKeyfile(!pedited || pedited->dirpyrDenoise.enabled, "Directional Pyramid Denoising", "Enabled", dirpyrDenoise.enabled, keyFile); @@ -4647,6 +4656,9 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) 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); + assignFromKeyfile(keyFile, "Dehaze", "Detail", pedited, dehaze.detail, pedited->dehaze.detail); } if (keyFile.has_group("Film Simulation")) { diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 19fe0a376..143fedbe8 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1230,6 +1230,9 @@ struct SoftLightParams { struct DehazeParams { bool enabled; int strength; + bool showDepthMap; + int depth; + int detail; DehazeParams(); diff --git a/rtgui/dehaze.cc b/rtgui/dehaze.cc index 6f4814e55..ef5075ab7 100644 --- a/rtgui/dehaze.cc +++ b/rtgui/dehaze.cc @@ -30,12 +30,30 @@ Dehaze::Dehaze(): FoldableToolPanel(this, "dehaze", M("TP_DEHAZE_LABEL"), false, 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"); + EvDehazeDetail = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_DETAIL"); 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"), -100., 100., 1., 0.)); + depth->setAdjusterListener(this); + depth->show(); + + detail = Gtk::manage(new Adjuster(M("TP_DEHAZE_DETAIL"), -100, 100, 1, 0)); + detail->setAdjusterListener(this); + detail->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(*detail); + pack_start(*showDepthMap); } @@ -45,11 +63,17 @@ void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) if (pedited) { strength->setEditedState(pedited->dehaze.strength ? Edited : UnEdited); + depth->setEditedState(pedited->dehaze.depth ? Edited : UnEdited); + detail->setEditedState(pedited->dehaze.detail ? 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); + detail->setValue(pp->dehaze.detail); + showDepthMap->set_active(pp->dehaze.showDepthMap); enableListener(); } @@ -58,22 +82,34 @@ void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) void Dehaze::write(ProcParams *pp, ParamsEdited *pedited) { pp->dehaze.strength = strength->getValue(); + pp->dehaze.depth = depth->getValue(); + pp->dehaze.detail = detail->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.detail = detail->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); + detail->setDefault(defParams->dehaze.detail); if (pedited) { strength->setDefaultEditedState(pedited->dehaze.strength ? Edited : UnEdited); + depth->setDefaultEditedState(pedited->dehaze.depth ? Edited : UnEdited); + detail->setDefaultEditedState(pedited->dehaze.detail ? Edited : UnEdited); } else { strength->setDefaultEditedState(Irrelevant); + depth->setDefaultEditedState(Irrelevant); + detail->setDefaultEditedState(Irrelevant); } } @@ -81,7 +117,13 @@ void Dehaze::setDefaults(const ProcParams *defParams, const ParamsEdited *pedite void Dehaze::adjusterChanged(Adjuster* a, double newval) { if (listener && getEnabled()) { - listener->panelChanged(EvDehazeStrength, a->getTextValue()); + if (a == strength) { + listener->panelChanged(EvDehazeStrength, a->getTextValue()); + } else if (a == depth) { + listener->panelChanged(EvDehazeDepth, a->getTextValue()); + } else if (a == detail) { + listener->panelChanged(EvDehazeDetail, a->getTextValue()); + } } } @@ -100,11 +142,21 @@ void Dehaze::enabledChanged () } +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(); + detail->showEditedCB(); } diff --git a/rtgui/dehaze.h b/rtgui/dehaze.h index 3617f13ea..ae6097c86 100644 --- a/rtgui/dehaze.h +++ b/rtgui/dehaze.h @@ -27,9 +27,15 @@ class Dehaze: public ToolParamBlock, public AdjusterListener, public FoldableToo { private: Adjuster *strength; + Adjuster *depth; + Adjuster *detail; + Gtk::CheckButton *showDepthMap; rtengine::ProcEvent EvDehazeEnabled; rtengine::ProcEvent EvDehazeStrength; + rtengine::ProcEvent EvDehazeDepth; + rtengine::ProcEvent EvDehazeDetail; + rtengine::ProcEvent EvDehazeShowDepthMap; public: @@ -42,6 +48,7 @@ public: void adjusterChanged(Adjuster *a, double newval); void enabledChanged(); + void showDepthMapChanged(); void setAdjusterBehavior(bool strengthAdd); }; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index fef8da8e6..ffc1bd19c 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -568,6 +568,9 @@ void ParamsEdited::set(bool v) softlight.strength = v; dehaze.enabled = v; dehaze.strength = v; + dehaze.showDepthMap = v; + dehaze.depth = v; + dehaze.detail = v; metadata.mode = v; exif = v; @@ -1123,6 +1126,9 @@ void ParamsEdited::initFrom(const std::vector& 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; + dehaze.detail = dehaze.detail && p.dehaze.detail == other.dehaze.detail; metadata.mode = metadata.mode && p.metadata.mode == other.metadata.mode; // How the hell can we handle that??? @@ -3125,6 +3131,18 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng 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.detail) { + toEdit.dehaze.detail = mods.dehaze.detail; + } + + 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 6a2076032..73a19db88 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -729,6 +729,9 @@ class DehazeParamsEdited public: bool enabled; bool strength; + bool showDepthMap; + bool depth; + bool detail; }; From 7c10f92ace3ba69ccccc314ceb49f6d9a5b16f7d Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Fri, 12 Oct 2018 16:01:48 +0200 Subject: [PATCH 06/12] dehaze: improved use of the guided filter for less halos --- rtengine/ipdehaze.cc | 152 ++++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index 7fa1988ce..355d7f843 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -58,28 +58,28 @@ namespace { #endif -int get_dark_channel(const Imagefloat &src, array2D &dst, +int get_dark_channel(const array2D &R, const array2D &G, const array2D &B, array2D &dst, int patchsize, float *ambient, bool multithread) { - const int w = src.getWidth(); - const int h = src.getHeight(); + 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 < src.getHeight(); y += patchsize) { - int pH = std::min(y+patchsize, h); - for (int x = 0; x < src.getWidth(); x += patchsize, ++npatches) { + for (int y = 0; y < H; y += patchsize) { + int pH = std::min(y+patchsize, H); + for (int x = 0; x < W; x += patchsize, ++npatches) { float val = RT_INFINITY_F; - int pW = std::min(x+patchsize, w); + int pW = std::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 = src.r(yy, xx); - float g = src.g(yy, xx); - float b = src.b(yy, xx); + float r = R[yy][xx]; + float g = G[yy][xx]; + float b = B[yy][xx]; if (ambient) { r /= ambient[0]; g /= ambient[1]; @@ -89,14 +89,16 @@ int get_dark_channel(const Imagefloat &src, array2D &dst, } val = min(val, yval); } + val = LIM01(val); for (int yy = y; yy < pH; ++yy) { std::fill(dst[yy]+x, dst[yy]+pW, val); } + float val2 = RT_INFINITY_F; for (int yy = y; yy < pH; ++yy) { for (int xx = x; xx < pW; ++xx) { - float r = src.r(yy, xx); - float g = src.g(yy, xx); - float b = src.b(yy, xx); + float r = R[yy][xx]; + float g = G[yy][xx]; + float b = B[yy][xx]; if (ambient) { r /= ambient[0]; g /= ambient[1]; @@ -104,7 +106,18 @@ int get_dark_channel(const Imagefloat &src, array2D &dst, } float l = min(r, g, b); if (l >= 2.f * val) { - dst[yy][xx] = l; + val2 = min(val2, l); + dst[yy][xx] = -1; + } + } + } + if (val2 < RT_INFINITY_F) { + val2 = LIM01(val2); + for (int yy = y; yy < pH; ++yy) { + for (int xx = x; xx < pW; ++xx) { + if (dst[yy][xx] < 0.f) { + dst[yy][xx] = val2; + } } } } @@ -115,10 +128,10 @@ int get_dark_channel(const Imagefloat &src, array2D &dst, } -int estimate_ambient_light(const Imagefloat *img, const array2D &dark, const array2D &Y, int patchsize, int npatches, float ambient[3]) +int estimate_ambient_light(const array2D &R, const array2D &G, const array2D &B, const array2D &dark, const array2D &Y, int patchsize, int npatches, float ambient[3]) { - const int W = img->getWidth(); - const int H = img->getHeight(); + const int W = R.width(); + const int H = R.height(); const auto get_percentile = [](std::priority_queue &q, float prcnt) -> float @@ -183,9 +196,9 @@ int estimate_ambient_light(const Imagefloat *img, const array2D &dark, co for (int y = p.second; y < pH; ++y) { for (int x = p.first; x < pW; ++x) { if (Y[y][x] >= lim) { - float r = img->r(y, x); - float g = img->g(y, x); - float b = img->b(y, x); + float r = R[y][x]; + float g = G[y][x]; + float b = B[y][x]; rr += r; gg += g; bb += b; @@ -218,41 +231,25 @@ void get_luminance(Imagefloat *img, array2D &Y, TMatrix ws, bool multithr } -void apply_contrast(array2D &dark, int contrast, double scale, bool multithread) +void apply_contrast(array2D &dark, float ambient, int contrast, double scale, bool multithread) { if (contrast) { const int W = dark.width(); const int H = dark.height(); - double tot = 0.0; -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - for (int y = 0; y < H; ++y) { - double ytot = 0.0; - for (int x = 0; x < W; ++x) { - ytot += dark[y][x]; - } -#ifdef _OPENMP - #pragma omp critical -#endif - { - tot += ytot; - } - } - - float avg = tot / (W * H); + float avg = ambient * 0.25f; + float c = contrast * 0.3f; std::vector pts = { DCT_NURBS, 0, //black point. Value in [0 ; 1] range 0, //black point. Value in [0 ; 1] range - avg - avg * (0.6 - contrast / 250.0), //toe point - avg - avg * (0.6 + contrast / 250.0), //value at toe point + avg - avg * (0.6 - c / 250.0), //toe point + avg - avg * (0.6 + c / 250.0), //value at toe point - avg + (1 - avg) * (0.6 - contrast / 250.0), //shoulder point - avg + (1 - avg) * (0.6 + contrast / 250.0), //value at shoulder point + avg + (1 - avg) * (0.6 - c / 250.0), //shoulder point + avg + (1 - avg) * (0.6 + c / 250.0), //value at shoulder point 1., // white point 1. // value at white point @@ -271,6 +268,28 @@ void apply_contrast(array2D &dark, int contrast, double scale, bool multi } } + +void extract_channels(Imagefloat *img, const array2D &Y, 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(Y, r, r, radius, epsilon, multithread); + guidedFilter(Y, g, g, radius, epsilon, multithread); + guidedFilter(Y, b, b, radius, epsilon, multithread); +} + } // namespace @@ -289,18 +308,24 @@ void ImProcFunctions::dehaze(Imagefloat *img) if (options.rtSettings.verbose) { std::cout << "dehaze: strength = " << strength << std::endl; } - - array2D dark(W, H); - const int patchsize = std::max(W / (200 + max(params->dehaze.detail, 0)), 2); - int npatches = get_dark_channel(*img, dark, patchsize, nullptr, multiThread); - DEBUG_DUMP(dark); TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); array2D Y(W, H); get_luminance(img, Y, ws, multiThread); + + array2D R(W, H); + array2D G(W, H); + array2D B(W, H); + int patchsize = max(int(20 / scale), 2); + extract_channels(img, Y, R, G, B, patchsize, 1e-1, multiThread); + array2D dark(W, H); + patchsize = std::max(W / (200 + params->dehaze.detail * (SGN(params->dehaze.detail) > 0 ? 4 : 1)), 2); + int npatches = get_dark_channel(R, G, B, dark, patchsize, nullptr, multiThread); + DEBUG_DUMP(dark); + float ambient[3]; - int n = estimate_ambient_light(img, dark, Y, patchsize, npatches, ambient); + int n = estimate_ambient_light(R, G, B, dark, Y, patchsize, npatches, ambient); float ambient_Y = Color::rgbLuminance(ambient[0], ambient[1], ambient[2], ws); if (options.rtSettings.verbose) { @@ -320,8 +345,8 @@ void ImProcFunctions::dehaze(Imagefloat *img) } array2D &t_tilde = dark; - get_dark_channel(*img, dark, patchsize, ambient, multiThread); - apply_contrast(dark, params->dehaze.depth, scale, multiThread); + get_dark_channel(R, G, B, dark, patchsize, ambient, multiThread); + apply_contrast(dark, ambient_Y, params->dehaze.depth, scale, multiThread); DEBUG_DUMP(t_tilde); if (!params->dehaze.showDepthMap) { @@ -344,7 +369,8 @@ void ImProcFunctions::dehaze(Imagefloat *img) const int radius = max(int(patchsize * mult), 1); const float epsilon = 2.5e-4; array2D &t = t_tilde; - + + if (!params->dehaze.showDepthMap) guidedFilter(Y, t_tilde, t, radius, epsilon, multiThread); DEBUG_DUMP(t); @@ -362,16 +388,26 @@ void ImProcFunctions::dehaze(Imagefloat *img) return; } - const float t0 = 0.01; + const float t0 = 0.1; + const float teps = 1e-3; #ifdef _OPENMP #pragma omp parallel for if (multiThread) #endif for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { - float mt = std::max(t[y][x], t0); - float r = (img->r(y, x) - ambient[0]) / mt + ambient[0]; - float g = (img->g(y, x) - ambient[1]) / mt + ambient[1]; - float b = (img->b(y, x) - ambient[2]) / mt + ambient[2]; + float rgb[3] = { img->r(y, x), img->g(y, x), img->b(y, x) }; + float tl = 1.f - min(rgb[0]/ambient[0], rgb[1]/ambient[1], rgb[2]/ambient[2]); + 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); + 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; @@ -388,7 +424,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) if (newmed > 1e-5f) { const float f1 = oldmed / newmed; - const float f = /* f1 * */ 65535.f; + const float f = f1 * 65535.f; #ifdef _OPENMP #pragma omp parallel for if (multiThread) #endif From 4d0ddd56e53e312a8d5fd29b61dd34708e91e615 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Tue, 16 Oct 2018 23:20:11 +0200 Subject: [PATCH 07/12] revamped and simplified dehaze -- now it's finally usable --- rtdata/languages/default | 2 - rtengine/ipdehaze.cc | 286 ++++++++++++--------------------------- rtengine/procparams.cc | 8 +- rtengine/procparams.h | 1 - rtgui/dehaze.cc | 18 +-- rtgui/dehaze.h | 2 - rtgui/paramsedited.cc | 6 - rtgui/paramsedited.h | 1 - 8 files changed, 87 insertions(+), 237 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index facf89498..7dcefdbc8 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -729,7 +729,6 @@ 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_DETAIL;Dehaze - Detail HISTORY_MSG_DEHAZE_ENABLED;Haze Removal HISTORY_MSG_DEHAZE_SHOW_DEPTH_MAP;Dehaze - Show depth map HISTORY_MSG_DEHAZE_STRENGTH;Dehaze - Strength @@ -1512,7 +1511,6 @@ TP_DEFRINGE_LABEL;Defringe TP_DEFRINGE_RADIUS;Radius TP_DEFRINGE_THRESHOLD;Threshold TP_DEHAZE_DEPTH;Depth -TP_DEHAZE_DETAIL;Detail TP_DEHAZE_LABEL;Haze Removal TP_DEHAZE_SHOW_DEPTH_MAP;Show Depth Map TP_DEHAZE_STRENGTH;Strength diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index 355d7f843..1c5512c2a 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -70,10 +70,10 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr #pragma omp parallel for if (multithread) #endif for (int y = 0; y < H; y += patchsize) { - int pH = std::min(y+patchsize, H); + int pH = min(y+patchsize, H); for (int x = 0; x < W; x += patchsize, ++npatches) { float val = RT_INFINITY_F; - int pW = std::min(x+patchsize, W); + 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) { @@ -93,34 +93,6 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr for (int yy = y; yy < pH; ++yy) { std::fill(dst[yy]+x, dst[yy]+pW, val); } - float val2 = RT_INFINITY_F; - for (int yy = y; yy < pH; ++yy) { - 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]; - } - float l = min(r, g, b); - if (l >= 2.f * val) { - val2 = min(val2, l); - dst[yy][xx] = -1; - } - } - } - if (val2 < RT_INFINITY_F) { - val2 = LIM01(val2); - for (int yy = y; yy < pH; ++yy) { - for (int xx = x; xx < pW; ++xx) { - if (dst[yy][xx] < 0.f) { - dst[yy][xx] = val2; - } - } - } - } } } @@ -128,7 +100,7 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr } -int estimate_ambient_light(const array2D &R, const array2D &G, const array2D &B, const array2D &dark, const array2D &Y, int patchsize, int npatches, float ambient[3]) +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(); @@ -143,7 +115,7 @@ int estimate_ambient_light(const array2D &R, const array2D &G, con return q.top(); }; - float lim = RT_INFINITY_F; + float darklim = RT_INFINITY_F; { std::priority_queue p; for (int y = 0; y < H; y += patchsize) { @@ -151,7 +123,7 @@ int estimate_ambient_light(const array2D &R, const array2D &G, con p.push(dark[y][x]); } } - lim = get_percentile(p, 0.95); + darklim = get_percentile(p, 0.95); } std::vector> patches; @@ -159,7 +131,7 @@ int estimate_ambient_light(const array2D &R, const array2D &G, con for (int y = 0; y < H; y += patchsize) { for (int x = 0; x < W; x += patchsize) { - if (dark[y][x] >= lim) { + if (dark[y][x] >= darklim) { patches.push_back(std::make_pair(x, y)); } } @@ -170,35 +142,36 @@ int estimate_ambient_light(const array2D &R, const array2D &G, con << " patches" << std::endl; } + float bright_lim = RT_INFINITY_F; { std::priority_queue l; for (auto &p : patches) { - const int pW = std::min(p.first+patchsize, W); - const int pH = std::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(Y[y][x]); + l.push(R[y][x] + G[y][x] + B[y][x]); } } } - lim = get_percentile(l, 0.95); + bright_lim = get_percentile(l, 0.95); } double rr = 0, gg = 0, bb = 0; int n = 0; for (auto &p : patches) { - const int pW = std::min(p.first+patchsize, W); - const int pH = std::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) { - if (Y[y][x] >= lim) { - float r = R[y][x]; - float g = G[y][x]; - float b = B[y][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; @@ -211,65 +184,12 @@ int estimate_ambient_light(const array2D &R, const array2D &G, con ambient[1] = gg / n; ambient[2] = bb / n; - return n; + // taken from darktable + return darklim > 0 ? -1.125f * std::log(darklim) : std::log(std::numeric_limits::max()) / 2; } -void get_luminance(Imagefloat *img, array2D &Y, TMatrix ws, 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) { - Y[y][x] = Color::rgbLuminance(img->r(y, x), img->g(y, x), img->b(y, x), ws); - } - } -} - - -void apply_contrast(array2D &dark, float ambient, int contrast, double scale, bool multithread) -{ - if (contrast) { - const int W = dark.width(); - const int H = dark.height(); - - float avg = ambient * 0.25f; - float c = contrast * 0.3f; - - std::vector pts = { - DCT_NURBS, - 0, //black point. Value in [0 ; 1] range - 0, //black point. Value in [0 ; 1] range - - avg - avg * (0.6 - c / 250.0), //toe point - avg - avg * (0.6 + c / 250.0), //value at toe point - - avg + (1 - avg) * (0.6 - c / 250.0), //shoulder point - avg + (1 - avg) * (0.6 + c / 250.0), //value at shoulder point - - 1., // white point - 1. // value at white point - }; - - const DiagonalCurve curve(pts, CURVES_MIN_POLY_POINTS / scale); - -#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] = curve.getVal(dark[y][x]); - } - } - } -} - - -void extract_channels(Imagefloat *img, const array2D &Y, array2D &r, array2D &g, array2D &b, int radius, float epsilon, bool multithread) +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(); @@ -285,11 +205,12 @@ void extract_channels(Imagefloat *img, const array2D &Y, array2D & } } - guidedFilter(Y, r, r, radius, epsilon, multithread); - guidedFilter(Y, g, g, radius, epsilon, multithread); - guidedFilter(Y, b, b, radius, epsilon, multithread); + guidedFilter(r, r, r, radius, epsilon, multithread, radius / 2); + guidedFilter(g, g, g, radius, epsilon, multithread, radius / 2); + guidedFilter(b, b, b, radius, epsilon, multithread, radius / 2); } + } // namespace @@ -309,31 +230,33 @@ void ImProcFunctions::dehaze(Imagefloat *img) std::cout << "dehaze: strength = " << strength << std::endl; } - TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); - array2D Y(W, H); - get_luminance(img, Y, ws, multiThread); - - array2D R(W, H); - array2D G(W, H); - array2D B(W, H); - int patchsize = max(int(20 / scale), 2); - extract_channels(img, Y, R, G, B, patchsize, 1e-1, multiThread); - array2D dark(W, H); - patchsize = std::max(W / (200 + params->dehaze.detail * (SGN(params->dehaze.detail) > 0 ? 4 : 1)), 2); - int npatches = get_dark_channel(R, G, B, dark, patchsize, nullptr, multiThread); - DEBUG_DUMP(dark); + int patchsize = max(int(5 / scale), 2); + int npatches = 0; float ambient[3]; - int n = estimate_ambient_light(R, G, B, dark, Y, patchsize, npatches, ambient); - float ambient_Y = Color::rgbLuminance(ambient[0], ambient[1], ambient[2], ws); + array2D &t_tilde = dark; + float max_t = 0.f; - if (options.rtSettings.verbose) { - std::cout << "dehaze: ambient light is " - << ambient[0] << ", " << ambient[1] << ", " << ambient[2] - << " (average of " << n << ")" - << std::endl; - std::cout << " ambient luminance is " << ambient_Y << std::endl; + { + 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, 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, multiThread); } if (min(ambient[0], ambient[1], ambient[2]) < 0.01f) { @@ -344,59 +267,41 @@ void ImProcFunctions::dehaze(Imagefloat *img) return; // probably no haze at all } - array2D &t_tilde = dark; - get_dark_channel(R, G, B, dark, patchsize, ambient, multiThread); - apply_contrast(dark, ambient_Y, params->dehaze.depth, scale, multiThread); DEBUG_DUMP(t_tilde); - if (!params->dehaze.showDepthMap) { -#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]; - } - } - } - - float mult = 2.f; - if (params->dehaze.detail > 0) { - mult -= (params->dehaze.detail / 100.f) * 1.9f; - } else { - mult -= params->dehaze.detail / 10.f; - } - const int radius = max(int(patchsize * mult), 1); - const float epsilon = 2.5e-4; - array2D &t = t_tilde; - - if (!params->dehaze.showDepthMap) - guidedFilter(Y, t_tilde, t, radius, epsilon, multiThread); - - DEBUG_DUMP(t); - - - if (params->dehaze.showDepthMap) { -#ifdef _OPENMP - #pragma omp parallel for if (multiThread) -#endif - for (int y = 0; y < H; ++y) { - for (int x = 0; x < W; ++x) { - img->r(y, x) = img->g(y, x) = img->b(y, x) = t[y][x] * 65535.f; - } - } - return; - } - - const float t0 = 0.1; - const float teps = 1e-3; #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-7; + array2D &t = t_tilde; + + { + array2D guideB(W, H, img->b.ptrs, ARRAY2D_BYREFERENCE); + guidedFilter(guideB, t_tilde, t, radius, epsilon, multiThread, patchsize); + } + + DEBUG_DUMP(t); + + 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) { @@ -404,44 +309,21 @@ void ImProcFunctions::dehaze(Imagefloat *img) } } float mt = max(t[y][x], t0, tl + teps, tu + teps); - 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 (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; - } - } - - float oldmed; - findMinMaxPercentile(Y, Y.width() * Y.height(), 0.5, oldmed, 0.5, oldmed, multiThread); - - get_luminance(img, Y, ws, multiThread); - float newmed; - - findMinMaxPercentile(Y, Y.width() * Y.height(), 0.5, newmed, 0.5, newmed, multiThread); - - if (newmed > 1e-5f) { - const float f1 = oldmed / newmed; - const float f = f1 * 65535.f; -#ifdef _OPENMP - #pragma omp parallel for if (multiThread) -#endif - for (int y = 0; y < H; ++y) { - for (int x = 0; x < W; ++x) { - float r = img->r(y, x); - float g = img->g(y, x); - float b = img->b(y, x); - float h, s, l; - Color::rgb2hslfloat(r * f, g * f, b * f, h, s, l); - s = LIM01(s / f1); - Color::hsl2rgbfloat(h, s, l, img->r(y, x), img->g(y, x), img->b(y, x)); + img->r(y, x) = r; + img->g(y, x) = g; + img->b(y, x) = b; } } - } else { - img->normalizeFloatTo65535(); } + + img->normalizeFloatTo65535(); } diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 17afb3371..78ee78f45 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -2378,8 +2378,7 @@ DehazeParams::DehazeParams() : enabled(false), strength(50), showDepthMap(false), - depth(0), - detail(0) + depth(25) { } @@ -2389,8 +2388,7 @@ bool DehazeParams::operator ==(const DehazeParams& other) const enabled == other.enabled && strength == other.strength && showDepthMap == other.showDepthMap - && depth == other.depth - && detail == other.detail; + && depth == other.depth; } bool DehazeParams::operator !=(const DehazeParams& other) const @@ -3070,7 +3068,6 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo 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); - saveToKeyfile(!pedited || pedited->dehaze.detail, "Dehaze", "Detail", dehaze.detail, keyFile); // Directional pyramid denoising saveToKeyfile(!pedited || pedited->dirpyrDenoise.enabled, "Directional Pyramid Denoising", "Enabled", dirpyrDenoise.enabled, keyFile); @@ -4658,7 +4655,6 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) 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); - assignFromKeyfile(keyFile, "Dehaze", "Detail", pedited, dehaze.detail, pedited->dehaze.detail); } if (keyFile.has_group("Film Simulation")) { diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 143fedbe8..d335ad029 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1232,7 +1232,6 @@ struct DehazeParams { int strength; bool showDepthMap; int depth; - int detail; DehazeParams(); diff --git a/rtgui/dehaze.cc b/rtgui/dehaze.cc index ef5075ab7..0f0892ac6 100644 --- a/rtgui/dehaze.cc +++ b/rtgui/dehaze.cc @@ -32,27 +32,21 @@ Dehaze::Dehaze(): FoldableToolPanel(this, "dehaze", M("TP_DEHAZE_LABEL"), false, 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"); - EvDehazeDetail = m->newEvent(HDR, "HISTORY_MSG_DEHAZE_DETAIL"); 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"), -100., 100., 1., 0.)); + depth = Gtk::manage(new Adjuster(M("TP_DEHAZE_DEPTH"), 0., 100., 1., 25.)); depth->setAdjusterListener(this); depth->show(); - detail = Gtk::manage(new Adjuster(M("TP_DEHAZE_DETAIL"), -100, 100, 1, 0)); - detail->setAdjusterListener(this); - detail->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(*detail); pack_start(*showDepthMap); } @@ -64,7 +58,6 @@ void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) if (pedited) { strength->setEditedState(pedited->dehaze.strength ? Edited : UnEdited); depth->setEditedState(pedited->dehaze.depth ? Edited : UnEdited); - detail->setEditedState(pedited->dehaze.detail ? Edited : UnEdited); set_inconsistent(multiImage && !pedited->dehaze.enabled); showDepthMap->set_inconsistent(!pedited->dehaze.showDepthMap); } @@ -72,7 +65,6 @@ void Dehaze::read(const ProcParams *pp, const ParamsEdited *pedited) setEnabled(pp->dehaze.enabled); strength->setValue(pp->dehaze.strength); depth->setValue(pp->dehaze.depth); - detail->setValue(pp->dehaze.detail); showDepthMap->set_active(pp->dehaze.showDepthMap); enableListener(); @@ -83,14 +75,12 @@ void Dehaze::write(ProcParams *pp, ParamsEdited *pedited) { pp->dehaze.strength = strength->getValue(); pp->dehaze.depth = depth->getValue(); - pp->dehaze.detail = detail->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.detail = detail->getEditedState(); pedited->dehaze.enabled = !get_inconsistent(); pedited->dehaze.showDepthMap = !showDepthMap->get_inconsistent(); } @@ -100,16 +90,13 @@ void Dehaze::setDefaults(const ProcParams *defParams, const ParamsEdited *pedite { strength->setDefault(defParams->dehaze.strength); depth->setDefault(defParams->dehaze.depth); - detail->setDefault(defParams->dehaze.detail); if (pedited) { strength->setDefaultEditedState(pedited->dehaze.strength ? Edited : UnEdited); depth->setDefaultEditedState(pedited->dehaze.depth ? Edited : UnEdited); - detail->setDefaultEditedState(pedited->dehaze.detail ? Edited : UnEdited); } else { strength->setDefaultEditedState(Irrelevant); depth->setDefaultEditedState(Irrelevant); - detail->setDefaultEditedState(Irrelevant); } } @@ -121,8 +108,6 @@ void Dehaze::adjusterChanged(Adjuster* a, double newval) listener->panelChanged(EvDehazeStrength, a->getTextValue()); } else if (a == depth) { listener->panelChanged(EvDehazeDepth, a->getTextValue()); - } else if (a == detail) { - listener->panelChanged(EvDehazeDetail, a->getTextValue()); } } } @@ -156,7 +141,6 @@ void Dehaze::setBatchMode(bool batchMode) strength->showEditedCB(); depth->showEditedCB(); - detail->showEditedCB(); } diff --git a/rtgui/dehaze.h b/rtgui/dehaze.h index ae6097c86..26cbef74a 100644 --- a/rtgui/dehaze.h +++ b/rtgui/dehaze.h @@ -28,13 +28,11 @@ class Dehaze: public ToolParamBlock, public AdjusterListener, public FoldableToo private: Adjuster *strength; Adjuster *depth; - Adjuster *detail; Gtk::CheckButton *showDepthMap; rtengine::ProcEvent EvDehazeEnabled; rtengine::ProcEvent EvDehazeStrength; rtengine::ProcEvent EvDehazeDepth; - rtengine::ProcEvent EvDehazeDetail; rtengine::ProcEvent EvDehazeShowDepthMap; public: diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index ffc1bd19c..610c6d3e2 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -570,7 +570,6 @@ void ParamsEdited::set(bool v) dehaze.strength = v; dehaze.showDepthMap = v; dehaze.depth = v; - dehaze.detail = v; metadata.mode = v; exif = v; @@ -1128,7 +1127,6 @@ void ParamsEdited::initFrom(const std::vector& 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; - dehaze.detail = dehaze.detail && p.dehaze.detail == other.dehaze.detail; metadata.mode = metadata.mode && p.metadata.mode == other.metadata.mode; // How the hell can we handle that??? @@ -3135,10 +3133,6 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.dehaze.depth = mods.dehaze.depth; } - if (dehaze.detail) { - toEdit.dehaze.detail = mods.dehaze.detail; - } - if (dehaze.showDepthMap) { toEdit.dehaze.showDepthMap = mods.dehaze.showDepthMap; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 73a19db88..3142b0136 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -731,7 +731,6 @@ public: bool strength; bool showDepthMap; bool depth; - bool detail; }; From 59339644de501ce539a9d35b25ead22a7e7f5518 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Oct 2018 15:38:35 +0200 Subject: [PATCH 08/12] dehaze: do not consider out-of-gamut pixels when estimating the ambient light --- rtengine/ipdehaze.cc | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index 1c5512c2a..37bd53704 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -58,8 +58,7 @@ namespace { #endif -int get_dark_channel(const array2D &R, const array2D &G, const array2D &B, array2D &dst, - int patchsize, float *ambient, bool multithread) +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(); @@ -89,7 +88,9 @@ int get_dark_channel(const array2D &R, const array2D &G, const arr } val = min(val, yval); } - val = LIM01(val); + if (clip) { + val = LIM01(val); + } for (int yy = y; yy < pH; ++yy) { std::fill(dst[yy]+x, dst[yy]+pW, val); } @@ -120,7 +121,9 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c std::priority_queue p; for (int y = 0; y < H; y += patchsize) { for (int x = 0; x < W; x += patchsize) { - p.push(dark[y][x]); + if (!OOG(dark[y][x], 1.f)) { + p.push(dark[y][x]); + } } } darklim = get_percentile(p, 0.95); @@ -131,7 +134,7 @@ float estimate_ambient_light(const array2D &R, const array2D &G, c for (int y = 0; y < H; y += patchsize) { for (int x = 0; x < W; x += patchsize) { - if (dark[y][x] >= darklim) { + if (dark[y][x] >= darklim && !OOG(dark[y][x], 1.f)) { patches.push_back(std::make_pair(x, y)); } } @@ -245,7 +248,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) 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, multiThread); + 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); @@ -256,7 +259,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) << std::endl; } - get_dark_channel(R, G, B, dark, patchsize, ambient, multiThread); + get_dark_channel(R, G, B, dark, patchsize, ambient, true, multiThread); } if (min(ambient[0], ambient[1], ambient[2]) < 0.01f) { @@ -289,6 +292,10 @@ void ImProcFunctions::dehaze(Imagefloat *img) 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; From 2026fe1d1715a77b94ba931a2f89c0f524fb61b9 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 18 Oct 2018 08:53:02 +0200 Subject: [PATCH 09/12] guided filter: added support for automatic computation of subsampling factor --- rtengine/guidedfilter.cc | 28 ++++++++++++++++++++++++++++ rtengine/guidedfilter.h | 2 +- rtengine/ipdehaze.cc | 8 ++++---- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/rtengine/guidedfilter.cc b/rtengine/guidedfilter.cc index f6b702a73..a3dccf298 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 = 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/ipdehaze.cc b/rtengine/ipdehaze.cc index 37bd53704..92eaa4062 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -208,9 +208,9 @@ void extract_channels(Imagefloat *img, array2D &r, array2D &g, arr } } - guidedFilter(r, r, r, radius, epsilon, multithread, radius / 2); - guidedFilter(g, g, g, radius, epsilon, multithread, radius / 2); - guidedFilter(b, b, b, radius, epsilon, multithread, radius / 2); + guidedFilter(r, r, r, radius, epsilon, multithread); + guidedFilter(g, g, g, radius, epsilon, multithread); + guidedFilter(b, b, b, radius, epsilon, multithread); } @@ -287,7 +287,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) { array2D guideB(W, H, img->b.ptrs, ARRAY2D_BYREFERENCE); - guidedFilter(guideB, t_tilde, t, radius, epsilon, multiThread, patchsize); + guidedFilter(guideB, t_tilde, t, radius, epsilon, multiThread); } DEBUG_DUMP(t); From c4ee5e611d22e1ec9721c6573689f5e21647da9c Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Fri, 26 Oct 2018 18:04:07 +0200 Subject: [PATCH 10/12] guided filter: reuse buffer across boxblur invocations --- rtengine/guidedfilter.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rtengine/guidedfilter.cc b/rtengine/guidedfilter.cc index a3dccf298..4e31fa300 100644 --- a/rtengine/guidedfilter.cc +++ b/rtengine/guidedfilter.cc @@ -135,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 = From 7fb90644d38cfe801e4ab74cee934d7229c903e8 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Sat, 27 Oct 2018 15:25:38 +0200 Subject: [PATCH 11/12] dehaze: tweak epsilon to avoid artifacts in corner cases --- rtengine/ipdehaze.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/ipdehaze.cc b/rtengine/ipdehaze.cc index 92eaa4062..fecc73e7d 100644 --- a/rtengine/ipdehaze.cc +++ b/rtengine/ipdehaze.cc @@ -282,7 +282,7 @@ void ImProcFunctions::dehaze(Imagefloat *img) } const int radius = patchsize * 4; - const float epsilon = 1e-7; + const float epsilon = 1e-5; array2D &t = t_tilde; { From 43876abb81471cabbd6219ce6188c68056fc35b2 Mon Sep 17 00:00:00 2001 From: Morgan Hardwood Date: Tue, 30 Oct 2018 23:56:49 +0100 Subject: [PATCH 12/12] Reverted changes to 'default' language file #4898 Commit 14ac4b accidentally wiped all non-ASCII characters from the file while adding new keys. This commit restores them. --- rtdata/languages/default | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index d9cc7f4a2..370d8645d 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -398,7 +398,7 @@ HISTORY_MSG_145;Microcontrast - Uniformity HISTORY_MSG_146;Edge sharpening HISTORY_MSG_147;ES - Luminance only HISTORY_MSG_148;Microcontrast -HISTORY_MSG_149;Microcontrast - 33 matrix +HISTORY_MSG_149;Microcontrast - 3×3 matrix HISTORY_MSG_150;Post-demosaic artifact/noise red. HISTORY_MSG_151;Vibrance HISTORY_MSG_152;Vib - Pastel tones @@ -812,7 +812,7 @@ IPTCPANEL_CITY;City IPTCPANEL_CITYHINT;Enter the name of the city pictured in this image. IPTCPANEL_COPYHINT;Copy IPTC settings to clipboard. IPTCPANEL_COPYRIGHT;Copyright notice -IPTCPANEL_COPYRIGHTHINT;Enter a Notice on the current owner of the Copyright for this image, such as 2008 Jane Doe. +IPTCPANEL_COPYRIGHTHINT;Enter a Notice on the current owner of the Copyright for this image, such as ©2008 Jane Doe. IPTCPANEL_COUNTRY;Country IPTCPANEL_COUNTRYHINT;Enter the name of the country pictured in this image. IPTCPANEL_CREATOR;Creator @@ -1372,9 +1372,9 @@ TP_COARSETRAF_TOOLTIP_ROTLEFT;Rotate left.\n\nShortcuts:\n[ - Multiple Ed TP_COARSETRAF_TOOLTIP_ROTRIGHT;Rotate right.\n\nShortcuts:\n] - Multiple Editor Tabs Mode,\nAlt-] - Single Editor Tab Mode. TP_COARSETRAF_TOOLTIP_VFLIP;Flip vertically. TP_COLORAPP_ADAPTSCENE;Scene absolute luminance -TP_COLORAPP_ADAPTSCENE_TOOLTIP;Absolute luminance of the scene environment (cd/m).\n1) Calculated from the Exif data:\nShutter speed - ISO speed - F number - camera exposure correction.\n2) Calculated from the raw white point and RT's Exposure Compensation slider. -TP_COLORAPP_ADAPTVIEWING;Viewing absolute luminance (cd/m) -TP_COLORAPP_ADAPTVIEWING_TOOLTIP;Absolute luminance of the viewing environment\n(usually 16cd/m). +TP_COLORAPP_ADAPTSCENE_TOOLTIP;Absolute luminance of the scene environment (cd/m²).\n1) Calculated from the Exif data:\nShutter speed - ISO speed - F number - camera exposure correction.\n2) Calculated from the raw white point and RT's Exposure Compensation slider. +TP_COLORAPP_ADAPTVIEWING;Viewing absolute luminance (cd/m²) +TP_COLORAPP_ADAPTVIEWING_TOOLTIP;Absolute luminance of the viewing environment\n(usually 16cd/m²). TP_COLORAPP_ADAP_AUTO_TOOLTIP;If the checkbox is checked (recommended) RawTherapee calculates an optimum value from the Exif data.\nTo set the value manually, uncheck the checkbox first. TP_COLORAPP_ALGO;Algorithm TP_COLORAPP_ALGO_ALL;All @@ -1411,7 +1411,7 @@ TP_COLORAPP_FREE;Free temp+green + CAT02 + [output] TP_COLORAPP_GAMUT;Gamut control (L*a*b*) TP_COLORAPP_GAMUT_TOOLTIP;Allow gamut control in L*a*b* mode. TP_COLORAPP_HUE;Hue (h) -TP_COLORAPP_HUE_TOOLTIP;Hue (h) - angle between 0 and 360. +TP_COLORAPP_HUE_TOOLTIP;Hue (h) - angle between 0° and 360°. TP_COLORAPP_LABEL;CIE Color Appearance Model 2002 TP_COLORAPP_LABEL_CAM02;Image Adjustments TP_COLORAPP_LABEL_SCENE;Scene Conditions @@ -1562,16 +1562,16 @@ TP_DIRPYRDENOISE_MEDIAN_METHOD_RGB;RGB TP_DIRPYRDENOISE_MEDIAN_METHOD_TOOLTIP;When using the "Luminance only" and "L*a*b*" methods, median filtering will be performed just after the wavelet step in the noise reduction pipeline.\nWhen using the "RGB" mode, it will be performed at the very end of the noise reduction pipeline. TP_DIRPYRDENOISE_MEDIAN_METHOD_WEIGHTED;Weighted L* (little) + a*b* (normal) TP_DIRPYRDENOISE_MEDIAN_PASSES;Median iterations -TP_DIRPYRDENOISE_MEDIAN_PASSES_TOOLTIP;Applying three median filter iterations with a 33 window size often leads to better results than using one median filter iteration with a 77 window size. +TP_DIRPYRDENOISE_MEDIAN_PASSES_TOOLTIP;Applying three median filter iterations with a 3×3 window size often leads to better results than using one median filter iteration with a 7×7 window size. TP_DIRPYRDENOISE_MEDIAN_TYPE;Median type -TP_DIRPYRDENOISE_MEDIAN_TYPE_TOOLTIP;Apply a median filter of the desired window size. The larger the window's size, the longer it takes.\n\n33 soft: treats 5 pixels in a 33 pixel window.\n33: treats 9 pixels in a 33 pixel window.\n55 soft: treats 13 pixels in a 55 pixel window.\n55: treats 25 pixels in a 55 pixel window.\n77: treats 49 pixels in a 77 pixel window.\n99: treats 81 pixels in a 99 pixel window.\n\nSometimes it is possible to achieve higher quality running several iterations with a smaller window size than one iteration with a larger one. +TP_DIRPYRDENOISE_MEDIAN_TYPE_TOOLTIP;Apply a median filter of the desired window size. The larger the window's size, the longer it takes.\n\n3×3 soft: treats 5 pixels in a 3×3 pixel window.\n3×3: treats 9 pixels in a 3×3 pixel window.\n5×5 soft: treats 13 pixels in a 5×5 pixel window.\n5×5: treats 25 pixels in a 5×5 pixel window.\n7×7: treats 49 pixels in a 7×7 pixel window.\n9×9: treats 81 pixels in a 9×9 pixel window.\n\nSometimes it is possible to achieve higher quality running several iterations with a smaller window size than one iteration with a larger one. TP_DIRPYRDENOISE_SLI;Slider -TP_DIRPYRDENOISE_TYPE_3X3;33 -TP_DIRPYRDENOISE_TYPE_3X3SOFT;33 soft -TP_DIRPYRDENOISE_TYPE_5X5;55 -TP_DIRPYRDENOISE_TYPE_5X5SOFT;55 soft -TP_DIRPYRDENOISE_TYPE_7X7;77 -TP_DIRPYRDENOISE_TYPE_9X9;99 +TP_DIRPYRDENOISE_TYPE_3X3;3×3 +TP_DIRPYRDENOISE_TYPE_3X3SOFT;3×3 soft +TP_DIRPYRDENOISE_TYPE_5X5;5×5 +TP_DIRPYRDENOISE_TYPE_5X5SOFT;5×5 soft +TP_DIRPYRDENOISE_TYPE_7X7;7×7 +TP_DIRPYRDENOISE_TYPE_9X9;9×9 TP_DIRPYREQUALIZER_ALGO;Skin Color Range TP_DIRPYREQUALIZER_ALGO_TOOLTIP;Fine: closer to the colors of the skin, minimizing the action on other colors\nLarge: avoid more artifacts. TP_DIRPYREQUALIZER_ARTIF;Reduce artifacts @@ -2017,7 +2017,7 @@ TP_SHARPENING_USM;Unsharp Mask TP_SHARPENMICRO_AMOUNT;Quantity TP_SHARPENMICRO_CONTRAST;Contrast threshold TP_SHARPENMICRO_LABEL;Microcontrast -TP_SHARPENMICRO_MATRIX;33 matrix instead of 55 +TP_SHARPENMICRO_MATRIX;3×3 matrix instead of 5×5 TP_SHARPENMICRO_UNIFORMITY;Uniformity TP_SOFTLIGHT_LABEL;Soft Light TP_SOFTLIGHT_STRENGTH;Strength