diff --git a/rtdata/languages/default b/rtdata/languages/default index 4ac12a093..963659884 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -751,6 +751,7 @@ HISTORY_MSG_RAWCACORR_AUTOIT;Raw CA Correction - Iterations HISTORY_MSG_RAWCACORR_COLORSHIFT;Raw CA Correction - Avoid color shift HISTORY_MSG_RAW_BORDER;Raw border HISTORY_MSG_RESIZE_ALLOWUPSCALING;Resize - Allow upscaling +HISTORY_MSG_SH_COLORSPACE;S/H - Colorspace HISTORY_MSG_SHARPENING_CONTRAST;Sharpening - Contrast threshold HISTORY_MSG_SOFTLIGHT_ENABLED;Soft light HISTORY_MSG_SOFTLIGHT_STRENGTH;Soft light - Strength diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 3cbfed183..759316e33 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -127,6 +127,7 @@ set(RTENGINESOURCEFILES xtrans_demosaic.cc vng4_demosaic_RT.cc ipsoftlight.cc + guidedfilter.cc ) if(LENSFUN_HAS_LOAD_DIRECTORY) diff --git a/rtengine/array2D.h b/rtengine/array2D.h index 25d644c85..48a789bf8 100644 --- a/rtengine/array2D.h +++ b/rtengine/array2D.h @@ -267,11 +267,11 @@ public: ar_realloc(w, h); memcpy(data, copy, w * h * sizeof(T)); } - int width() + int width() const { return x; } - int height() + int height() const { return y; } 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 new file mode 100644 index 000000000..f6b702a73 --- /dev/null +++ b/rtengine/guidedfilter.cc @@ -0,0 +1,195 @@ +/* -*- 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 . + */ + +/** + * This is a Fast Guided Filter implementation, derived directly from the + * pseudo-code of the paper: + * + * Fast Guided Filter + * by Kaiming He, Jian Sun + * + * available at https://arxiv.org/abs/1505.00996 + */ + +#include "guidedfilter.h" +#include "boxblur.h" +#include "rescale.h" +#include "imagefloat.h" + +namespace rtengine { + +#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 + + +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(); + + enum Op { MUL, DIVEPSILON, ADD, SUB, ADDMUL, SUBMUL }; + + const auto apply = + [=](Op op, array2D &res, const array2D &a, const array2D &b, const array2D &c=array2D()) -> void + { + const int w = res.width(); + const int h = res.height(); + +#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; + float aa = a[y][x]; + float bb = b[y][x]; + switch (op) { + case MUL: + r = aa * bb; + break; + case DIVEPSILON: + r = aa / (bb + epsilon); + break; + case ADD: + r = aa + bb; + break; + case SUB: + r = aa - bb; + break; + case ADDMUL: + r = aa * bb + c[y][x]; + break; + case SUBMUL: + r = c[y][x] - (aa * bb); + break; + default: + assert(false); + r = 0; + break; + } + res[y][x] = r; + } + } + }; + + // use the terminology of the paper (Algorithm 2) + const array2D &I = guide; + const array2D &p = src; + array2D &q = dst; + + const auto f_mean = + [](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()); + }; + + const auto f_subsample = + [=](array2D &d, const array2D &s) -> void + { + rescaleBilinear(s, d, multithread); + }; + + const auto f_upsample = f_subsample; + + const int w = W / subsampling; + const int h = H / subsampling; + + array2D I1(w, h); + array2D p1(w, h); + + f_subsample(I1, I); + f_subsample(p1, p); + + DEBUG_DUMP(I); + DEBUG_DUMP(p); + DEBUG_DUMP(I1); + DEBUG_DUMP(p1); + + float r1 = float(r) / subsampling; + + array2D meanI(w, h); + f_mean(meanI, I1, r1); + DEBUG_DUMP(meanI); + + array2D meanp(w, h); + f_mean(meanp, p1, r1); + DEBUG_DUMP(meanp); + + array2D &corrIp = p1; + apply(MUL, corrIp, I1, p1); + f_mean(corrIp, corrIp, r1); + DEBUG_DUMP(corrIp); + + array2D &corrI = I1; + apply(MUL, corrI, I1, I1); + f_mean(corrI, corrI, r1); + DEBUG_DUMP(corrI); + + array2D &varI = corrI; + apply(SUBMUL, varI, meanI, meanI, corrI); + DEBUG_DUMP(varI); + + array2D &covIp = corrIp; + apply(SUBMUL, covIp, meanI, meanp, corrIp); + DEBUG_DUMP(covIp); + + array2D &a = varI; + apply(DIVEPSILON, a, covIp, varI); + DEBUG_DUMP(a); + + array2D &b = covIp; + apply(SUBMUL, b, a, meanI, meanp); + DEBUG_DUMP(b); + + array2D &meana = a; + f_mean(meana, a, r1); + DEBUG_DUMP(meana); + + array2D &meanb = b; + f_mean(meanb, b, r1); + DEBUG_DUMP(meanb); + + array2D meanA(W, H); + f_upsample(meanA, meana); + DEBUG_DUMP(meanA); + + array2D &meanB = q; + f_upsample(meanB, meanb); + DEBUG_DUMP(meanB); + + apply(ADDMUL, q, meanA, I, meanB); + DEBUG_DUMP(q); +} + +} // namespace rtengine diff --git a/rtengine/guidedfilter.h b/rtengine/guidedfilter.h new file mode 100644 index 000000000..3f987f80e --- /dev/null +++ b/rtengine/guidedfilter.h @@ -0,0 +1,29 @@ +/* -*- 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 "array2D.h" + +namespace rtengine { + +void guidedFilter(const array2D &guide, const array2D &src, array2D &dst, int r, float epsilon, bool multithread, int subsampling=4); + +} // namespace rtengine diff --git a/rtengine/ipshadowshighlights.cc b/rtengine/ipshadowshighlights.cc index 4d5cc465a..ff56c84ac 100644 --- a/rtengine/ipshadowshighlights.cc +++ b/rtengine/ipshadowshighlights.cc @@ -22,6 +22,7 @@ #include "gauss.h" #include "sleef.c" #include "opthelper.h" +#include "guidedfilter.h" namespace rtengine { @@ -33,11 +34,32 @@ void ImProcFunctions::shadowsHighlights(LabImage *lab) const int width = lab->W; const int height = lab->H; + const bool lab_mode = params->sh.lab; array2D mask(width, height); - const float sigma = params->sh.radius * 5.f / scale; - LUTf f(32768); + array2D L(width, height); + const float radius = float(params->sh.radius) * 10 / scale; + LUTf f(lab_mode ? 32768 : 65536); + TMatrix ws = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); + TMatrix iws = ICCStore::getInstance()->workingSpaceInverseMatrix(params->icm.workingProfile); + + const auto rgb2lab = + [&](float R, float G, float B, float &l, float &a, float &b) -> void + { + float x, y, z; + Color::rgbxyz(R, G, B, x, y, z, ws); + Color::XYZ2Lab(x, y, z, l, a, b); + }; + + const auto lab2rgb = + [&](float l, float a, float b, float &R, float &G, float &B) -> void + { + float x, y, z; + Color::Lab2XYZ(l, a, b, x, y, z); + Color::xyz2rgb(x, y, z, R, G, B, iws); + }; + const auto apply = [&](int amount, int tonalwidth, bool hl) -> void { @@ -45,27 +67,24 @@ void ImProcFunctions::shadowsHighlights(LabImage *lab) const float scale = hl ? (thresh > 0.f ? 0.9f / thresh : 1.f) : thresh * 0.9f; #ifdef _OPENMP - #pragma omp parallel if (multiThread) + #pragma omp parallel for if (multiThread) #endif - { - -#ifdef _OPENMP - #pragma omp for -#endif - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - float l = lab->L[y][x]; - if (hl) { - mask[y][x] = (l > thresh) ? 1.f : pow4(l * scale); - } else { - mask[y][x] = l <= thresh ? 1.f : pow4(scale / l); - } + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + float l = lab->L[y][x]; + float l1 = l / 32768.f; + if (hl) { + mask[y][x] = (l > thresh) ? 1.f : pow4(l * scale); + L[y][x] = 1.f - l1; + } else { + mask[y][x] = l <= thresh ? 1.f : pow4(scale / l); + L[y][x] = l1; } } - - gaussianBlur(mask, mask, width, height, sigma); } + guidedFilter(L, mask, mask, radius, 0.075, multiThread, 4); + const float base = std::pow(4.f, float(amount)/100.f); const float gamma = hl ? base : 1.f / base; @@ -80,35 +99,55 @@ void ImProcFunctions::shadowsHighlights(LabImage *lab) }); if(!hl) { + if (lab_mode) { #ifdef _OPENMP - #pragma omp parallel for if (multiThread) + #pragma omp parallel for if (multiThread) #endif - for (int l = 0; l < 32768; ++l) { - auto base = pow_F(l / 32768.f, gamma); - // get a bit more contrast in the shadows - base = sh_contrast.getVal(base); - f[l] = base * 32768.f; + for (int l = 0; l < 32768; ++l) { + auto base = pow_F(l / 32768.f, gamma); + // get a bit more contrast in the shadows + base = sh_contrast.getVal(base); + f[l] = base * 32768.f; + } + } else { +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int c = 0; c < 65536; ++c) { + float l, a, b; + float R = c, G = c, B = c; + rgb2lab(R, G, B, l, a, b); + auto base = pow_F(l / 32768.f, gamma); + // get a bit more contrast in the shadows + base = sh_contrast.getVal(base); + l = base * 32768.f; + lab2rgb(l, a, b, R, G, B); + f[c] = G; + } } } else { -#ifdef __SSE2__ - vfloat c32768v = F2V(32768.f); - vfloat lv = _mm_setr_ps(0,1,2,3); - vfloat fourv = F2V(4.f); - vfloat gammav = F2V(gamma); - for (int l = 0; l < 32768; l += 4) { - vfloat basev = pow_F(lv / c32768v, gammav); - STVFU(f[l], basev * c32768v); - lv += fourv; - } -#else + if (lab_mode) { #ifdef _OPENMP - #pragma omp parallel for if (multiThread) + #pragma omp parallel for if (multiThread) #endif - for (int l = 0; l < 32768; ++l) { - auto base = pow_F(l / 32768.f, gamma); - f[l] = base * 32768.f; + for (int l = 0; l < 32768; ++l) { + auto base = pow_F(l / 32768.f, gamma); + f[l] = base * 32768.f; + } + } else { +#ifdef _OPENMP + #pragma omp parallel for if (multiThread) +#endif + for (int c = 0; c < 65536; ++c) { + float l, a, b; + float R = c, G = c, B = c; + rgb2lab(R, G, B, l, a, b); + auto base = pow_F(l / 32768.f, gamma); + l = base * 32768.f; + lab2rgb(l, a, b, R, G, B); + f[c] = G; + } } -#endif } #ifdef _OPENMP @@ -117,17 +156,26 @@ void ImProcFunctions::shadowsHighlights(LabImage *lab) for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { float l = lab->L[y][x]; - float blend = mask[y][x]; + float blend = LIM01(mask[y][x]); float orig = 1.f - blend; if (l >= 0.f && l < 32768.f) { - lab->L[y][x] = f[l] * blend + l * orig; - if (!hl && l > 1.f) { - // when pushing shadows, scale also the chromaticity - float s = max(lab->L[y][x] / l * 0.5f, 1.f) * blend; - float a = lab->a[y][x]; - float b = lab->b[y][x]; - lab->a[y][x] = a * s + a * orig; - lab->b[y][x] = b * s + b * orig; + if (lab_mode) { + lab->L[y][x] = f[l] * blend + l * orig; + if (!hl && l > 1.f) { + // when pushing shadows, scale also the chromaticity + float s = max(lab->L[y][x] / l * 0.5f, 1.f) * blend; + float a = lab->a[y][x]; + float b = lab->b[y][x]; + lab->a[y][x] = a * s + a * orig; + lab->b[y][x] = b * s + b * orig; + } + } else { + float rgb[3]; + lab2rgb(l, lab->a[y][x], lab->b[y][x], rgb[0], rgb[1], rgb[2]); + for (int i = 0; i < 3; ++i) { + rgb[i] = f[rgb[i]] * blend + rgb[i] * orig; + } + rgb2lab(rgb[0], rgb[1], rgb[2], lab->L[y][x], lab->a[y][x], lab->b[y][x]); } } } @@ -135,11 +183,11 @@ void ImProcFunctions::shadowsHighlights(LabImage *lab) }; if (params->sh.highlights > 0) { - apply(params->sh.highlights, params->sh.htonalwidth, true); + apply(params->sh.highlights * 0.7, params->sh.htonalwidth, true); } if (params->sh.shadows > 0) { - apply(params->sh.shadows, params->sh.stonalwidth, false); + apply(params->sh.shadows * 0.6, params->sh.stonalwidth, false); } } diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index d8d6aee33..66293abbb 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1501,7 +1501,8 @@ SHParams::SHParams() : htonalwidth(70), shadows(0), stonalwidth(30), - radius(40) + radius(40), + lab(false) { } @@ -1513,7 +1514,8 @@ bool SHParams::operator ==(const SHParams& other) const && htonalwidth == other.htonalwidth && shadows == other.shadows && stonalwidth == other.stonalwidth - && radius == other.radius; + && radius == other.radius + && lab == other.lab; } bool SHParams::operator !=(const SHParams& other) const @@ -3090,6 +3092,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->sh.shadows, "Shadows & Highlights", "Shadows", sh.shadows, keyFile); saveToKeyfile(!pedited || pedited->sh.stonalwidth, "Shadows & Highlights", "ShadowTonalWidth", sh.stonalwidth, keyFile); saveToKeyfile(!pedited || pedited->sh.radius, "Shadows & Highlights", "Radius", sh.radius, keyFile); + saveToKeyfile(!pedited || pedited->sh.lab, "Shadows & Highlights", "Lab", sh.lab, keyFile); // Crop saveToKeyfile(!pedited || pedited->crop.enabled, "Crop", "Enabled", crop.enabled, keyFile); @@ -4022,6 +4025,11 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Shadows & Highlights", "Shadows", pedited, sh.shadows, pedited->sh.shadows); assignFromKeyfile(keyFile, "Shadows & Highlights", "ShadowTonalWidth", pedited, sh.stonalwidth, pedited->sh.stonalwidth); assignFromKeyfile(keyFile, "Shadows & Highlights", "Radius", pedited, sh.radius, pedited->sh.radius); + if (ppVersion >= 344) { + assignFromKeyfile(keyFile, "Shadows & Highlights", "Lab", pedited, sh.lab, pedited->sh.lab); + } else { + sh.lab = true; + } if (keyFile.has_key("Shadows & Highlights", "LocalContrast") && ppVersion < 329) { int lc = keyFile.get_integer("Shadows & Highlights", "LocalContrast"); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 0b8b5ba56..700c6271c 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -749,6 +749,7 @@ struct SHParams { int shadows; int stonalwidth; int radius; + bool lab; SHParams(); diff --git a/rtengine/rescale.h b/rtengine/rescale.h new file mode 100644 index 000000000..ba9a01c99 --- /dev/null +++ b/rtengine/rescale.h @@ -0,0 +1,99 @@ +/* -*- 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 "array2D.h" + +namespace rtengine { + +inline float getBilinearValue(const array2D &src, float x, float y) +{ + const int W = src.width(); + const int H = src.height(); + + // Get integer and fractional parts of numbers + int xi = x; + int yi = y; + float xf = x - xi; + float yf = y - yi; + int xi1 = std::min(xi + 1, W - 1); + int yi1 = std::min(yi + 1, H - 1); + + float bl = src[yi][xi]; + float br = src[yi][xi1]; + float tl = src[yi1][xi]; + float tr = src[yi1][xi1]; + + // interpolate + float b = xf * br + (1.f - xf) * bl; + float t = xf * tr + (1.f - xf) * tl; + float pxf = yf * t + (1.f - yf) * b; + return pxf; +} + + +inline void rescaleBilinear(const array2D &src, array2D &dst, bool multithread) +{ + const int Ws = src.width(); + const int Hs = src.height(); + const int Wd = dst.width(); + const int Hd = dst.height(); + + float col_scale = float (Ws) / float (Wd); + float row_scale = float (Hs) / float (Hd); + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + + for (int y = 0; y < Hd; ++y) { + float ymrs = y * row_scale; + + for (int x = 0; x < Wd; ++x) { + dst[y][x] = getBilinearValue(src, x * col_scale, ymrs); + } + } +} + + +inline void rescaleNearest(const array2D &src, array2D &dst, bool multithread) +{ + const int width = src.width(); + const int height = src.height(); + const int nw = dst.width(); + const int nh = dst.height(); + +#ifdef _OPENMP + #pragma omp parallel for if (multithread) +#endif + + for (int y = 0; y < nh; ++y) { + int sy = y * height / nh; + + for (int x = 0; x < nw; ++x) { + int sx = x * width / nw; + dst[y][x] = src[sy][sx]; + } + } +} + + +} // namespace rtengine diff --git a/rtengine/tmo_fattal02.cc b/rtengine/tmo_fattal02.cc index 359b33575..dc7826501 100644 --- a/rtengine/tmo_fattal02.cc +++ b/rtengine/tmo_fattal02.cc @@ -73,6 +73,7 @@ #include "sleef.c" #include "opthelper.h" #include "rt_algo.h" +#include "rescale.h" namespace rtengine { @@ -938,66 +939,14 @@ void solve_pde_fft (Array2Df *F, Array2Df *U, Array2Df *buf, bool multithread)/* * RT code from here on *****************************************************************************/ -inline float get_bilinear_value (const Array2Df &src, float x, float y) +inline void rescale_bilinear (const Array2Df &src, Array2Df &dst, bool multithread) { - // Get integer and fractional parts of numbers - int xi = x; - int yi = y; - float xf = x - xi; - float yf = y - yi; - int xi1 = std::min (xi + 1, src.getCols() - 1); - int yi1 = std::min (yi + 1, src.getRows() - 1); - - float bl = src (xi, yi); - float br = src (xi1, yi); - float tl = src (xi, yi1); - float tr = src (xi1, yi1); - - // interpolate - float b = xf * br + (1.f - xf) * bl; - float t = xf * tr + (1.f - xf) * tl; - float pxf = yf * t + (1.f - yf) * b; - return pxf; + rescaleBilinear(src, dst, multithread); } - -void rescale_bilinear (const Array2Df &src, Array2Df &dst, bool multithread) +inline void rescale_nearest (const Array2Df &src, Array2Df &dst, bool multithread) { - float col_scale = float (src.getCols()) / float (dst.getCols()); - float row_scale = float (src.getRows()) / float (dst.getRows()); - -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - - for (int y = 0; y < dst.getRows(); ++y) { - float ymrs = y * row_scale; - - for (int x = 0; x < dst.getCols(); ++x) { - dst (x, y) = get_bilinear_value (src, x * col_scale, ymrs); - } - } -} - -void rescale_nearest (const Array2Df &src, Array2Df &dst, bool multithread) -{ - const int width = src.getCols(); - const int height = src.getRows(); - const int nw = dst.getCols(); - const int nh = dst.getRows(); - -#ifdef _OPENMP - #pragma omp parallel for if (multithread) -#endif - - for (int y = 0; y < nh; ++y) { - int sy = y * height / nh; - - for (int x = 0; x < nw; ++x) { - int sx = x * width / nw; - dst (x, y) = src (sx, sy); - } - } + rescaleNearest(src, dst, multithread); } diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 2ccb62f65..342d9adf9 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -292,6 +292,7 @@ void ParamsEdited::set(bool v) sh.shadows = v; sh.stonalwidth = v; sh.radius = v; + sh.lab = v; crop.enabled = v; crop.x = v; crop.y = v; @@ -851,6 +852,7 @@ void ParamsEdited::initFrom(const std::vector& sh.shadows = sh.shadows && p.sh.shadows == other.sh.shadows; sh.stonalwidth = sh.stonalwidth && p.sh.stonalwidth == other.sh.stonalwidth; sh.radius = sh.radius && p.sh.radius == other.sh.radius; + sh.lab = sh.lab && p.sh.lab == other.sh.lab; crop.enabled = crop.enabled && p.crop.enabled == other.crop.enabled; crop.x = crop.x && p.crop.x == other.crop.x; crop.y = crop.y && p.crop.y == other.crop.y; @@ -2116,6 +2118,10 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.sh.radius = mods.sh.radius; } + if (sh.lab) { + toEdit.sh.lab = mods.sh.lab; + } + if (crop.enabled) { toEdit.crop.enabled = mods.crop.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 0a795696c..177b05d85 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -402,12 +402,12 @@ class SHParamsEdited public: bool enabled; - bool hq; bool highlights; bool htonalwidth; bool shadows; bool stonalwidth; bool radius; + bool lab; }; class CropParamsEdited diff --git a/rtgui/ppversion.h b/rtgui/ppversion.h index 050c4c653..19e8c995d 100644 --- a/rtgui/ppversion.h +++ b/rtgui/ppversion.h @@ -1,11 +1,13 @@ #pragma once // This number has to be incremented whenever the PP3 file format is modified or the behaviour of a tool changes -#define PPVERSION 343 +#define PPVERSION 344 #define PPVERSION_AEXP 301 //value of PPVERSION when auto exposure algorithm was modified /* Log of version changes + 344 2018-10-04 + added Lab/RGB color space selection for shadows/highlights 343 2018-09-06 raw auto ca correction avoid colour shift 342 2018-09-05 diff --git a/rtgui/shadowshighlights.cc b/rtgui/shadowshighlights.cc index f1d8dbaf2..526ee9aae 100644 --- a/rtgui/shadowshighlights.cc +++ b/rtgui/shadowshighlights.cc @@ -17,12 +17,26 @@ * along with RawTherapee. If not, see . */ #include "shadowshighlights.h" +#include "eventmapper.h" using namespace rtengine; using namespace rtengine::procparams; ShadowsHighlights::ShadowsHighlights () : FoldableToolPanel(this, "shadowshighlights", M("TP_SHADOWSHLIGHTS_LABEL"), false, true) { + auto m = ProcEventMapper::getInstance(); + EvSHColorspace = m->newEvent(RGBCURVE, "HISTORY_MSG_SH_COLORSPACE"); + + Gtk::HBox* hb = Gtk::manage (new Gtk::HBox ()); + hb->pack_start(*Gtk::manage(new Gtk::Label(M("TP_DIRPYRDENOISE_MAIN_COLORSPACE") + ": ")), Gtk::PACK_SHRINK); + colorspace = Gtk::manage(new MyComboBoxText()); + colorspace->append(M("TP_DIRPYRDENOISE_MAIN_COLORSPACE_RGB")); + colorspace->append(M("TP_DIRPYRDENOISE_MAIN_COLORSPACE_LAB")); + hb->pack_start(*colorspace); + pack_start(*hb); + + pack_start (*Gtk::manage (new Gtk::HSeparator())); + highlights = Gtk::manage (new Adjuster (M("TP_SHADOWSHLIGHTS_HIGHLIGHTS"), 0, 100, 1, 0)); h_tonalwidth = Gtk::manage (new Adjuster (M("TP_SHADOWSHLIGHTS_HLTONALW"), 10, 100, 1, 70)); pack_start (*highlights); @@ -46,6 +60,8 @@ ShadowsHighlights::ShadowsHighlights () : FoldableToolPanel(this, "shadowshighli shadows->setAdjusterListener (this); s_tonalwidth->setAdjusterListener (this); + colorspace->signal_changed().connect(sigc::mem_fun(*this, &ShadowsHighlights::colorspaceChanged)); + show_all_children (); } @@ -61,6 +77,7 @@ void ShadowsHighlights::read (const ProcParams* pp, const ParamsEdited* pedited) shadows->setEditedState (pedited->sh.shadows ? Edited : UnEdited); s_tonalwidth->setEditedState (pedited->sh.stonalwidth ? Edited : UnEdited); set_inconsistent (multiImage && !pedited->sh.enabled); + } setEnabled (pp->sh.enabled); @@ -71,6 +88,14 @@ void ShadowsHighlights::read (const ProcParams* pp, const ParamsEdited* pedited) shadows->setValue (pp->sh.shadows); s_tonalwidth->setValue (pp->sh.stonalwidth); + if (pedited && !pedited->sh.lab) { + colorspace->set_active(2); + } else if (pp->sh.lab) { + colorspace->set_active(1); + } else { + colorspace->set_active(0); + } + enableListener (); } @@ -84,6 +109,12 @@ void ShadowsHighlights::write (ProcParams* pp, ParamsEdited* pedited) pp->sh.stonalwidth = (int)s_tonalwidth->getValue (); pp->sh.enabled = getEnabled(); + if (colorspace->get_active_row_number() == 0) { + pp->sh.lab = false; + } else if (colorspace->get_active_row_number() == 1) { + pp->sh.lab = true; + } + if (pedited) { pedited->sh.radius = radius->getEditedState (); pedited->sh.highlights = highlights->getEditedState (); @@ -91,6 +122,7 @@ void ShadowsHighlights::write (ProcParams* pp, ParamsEdited* pedited) pedited->sh.shadows = shadows->getEditedState (); pedited->sh.stonalwidth = s_tonalwidth->getEditedState (); pedited->sh.enabled = !get_inconsistent(); + pedited->sh.lab = colorspace->get_active_row_number() != 2; } } @@ -153,6 +185,13 @@ void ShadowsHighlights::enabledChanged () } } +void ShadowsHighlights::colorspaceChanged() +{ + if (listener && (multiImage || getEnabled()) ) { + listener->panelChanged(EvSHColorspace, colorspace->get_active_text()); + } +} + void ShadowsHighlights::setBatchMode (bool batchMode) { @@ -162,6 +201,7 @@ void ShadowsHighlights::setBatchMode (bool batchMode) h_tonalwidth->showEditedCB (); shadows->showEditedCB (); s_tonalwidth->showEditedCB (); + colorspace->append(M("GENERAL_UNCHANGED")); } void ShadowsHighlights::setAdjusterBehavior (bool hadd, bool sadd) diff --git a/rtgui/shadowshighlights.h b/rtgui/shadowshighlights.h index 96642bfc0..598eece68 100644 --- a/rtgui/shadowshighlights.h +++ b/rtgui/shadowshighlights.h @@ -32,6 +32,9 @@ protected: Adjuster* shadows; Adjuster* s_tonalwidth; Adjuster* radius; + MyComboBoxText *colorspace; + + rtengine::ProcEvent EvSHColorspace; public: @@ -47,6 +50,8 @@ public: void setAdjusterBehavior (bool hadd, bool sadd); void trimValues (rtengine::procparams::ProcParams* pp); + + void colorspaceChanged(); }; #endif