diff --git a/rtdata/languages/default b/rtdata/languages/default index e3437d07c..fdcab9568 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1404,6 +1404,16 @@ HISTORY_MSG_COLORTONING_LABREGION_SHOWMASK;CT - region show mask HISTORY_MSG_COLORTONING_LABREGION_SLOPE;CT - region slope HISTORY_MSG_COMPLEX;Wavelet complexity HISTORY_MSG_COMPLEXRETI;Retinex complexity +HISTORY_MSG_CG_COLORSPACE;Gamut Compression - Target +HISTORY_MSG_CG_CYANTH;Gamut Compression - Threshold Cyan +HISTORY_MSG_CG_MAGENTATH;Gamut Compression - Threshold Magenta +HISTORY_MSG_CG_YELLOWTH;Gamut Compression - Threshold Yellow +HISTORY_MSG_CG_CYANLIM;Gamut Compression - Max Limits Cyan +HISTORY_MSG_CG_MAGENTALIM;Gamut Compression - Max Limits Magenta +HISTORY_MSG_CG_YELLOWLIM;Gamut Compression - Max Limits Yellow +HISTORY_MSG_CG_ROLLOFF;Gamut Compression - Rolloff +HISTORY_MSG_CG_VALUE;Gamut Compression - Power +HISTORY_MSG_CG_ENABLED;Gamut Compression - Enabled HISTORY_MSG_DEHABLACK;Dehaze Bayer Black HISTORY_MSG_DEHABLACKX;Dehaze X-Trans Black HISTORY_MSG_DEHAZE_DEPTH;Dehaze - Depth @@ -2504,6 +2514,28 @@ TP_COLORTONING_TWOALL;Special chroma TP_COLORTONING_TWOBY;Special a* and b* TP_COLORTONING_TWOCOLOR_TOOLTIP;Standard chroma:\nLinear response, a* = b*.\n\nSpecial chroma:\nLinear response, a* = b*, but unbound - try under the diagonal.\n\nSpecial a* and b*:\nLinear response unbound with separate curves for a* and b*. Intended for special effects.\n\nSpecial chroma 2 colors:\nMore predictable. TP_COLORTONING_TWOSTD;Standard chroma +TP_COMPRESSGAMUT_LABEL;Gamut Compression +TP_COMPRESSGAMUT_MAIN_COLORSPACE;Target Compression Gamut +TP_COMPRESSGAMUT_COLORSPACE_TOOLTIP;Gamut-compress is a tool which allows you to compress highly chromatic camera source colorimetry into a smaller gamut.\n Out of gamut negative pixel values are problematic for working on images and should be avoided if possible.\n Blue LED are perceive as highly colorful and colors like beetles, flowers and hummingbirds often over saturated.\n\n This is not a gamut conversionĀ : converting from one gamut to another does change the values of the pixels. However it does not actually change the colorimetric meaning of the pixel data. You could say that by converting between gamuts, you are changing frame of reference to the same colors. After compression the working profile is restored.\n\n Choose the workspace that will be used to set the gamut limits. Depending on the case you can choose:\n The display gamut (sRGB, DCI P3,...). \n The gamut of an output file for use by another software (Prophoto, Rec 2020...). +TP_COMPRESSGAMUT_THRESHOLD_TOOLTIP;Threshold: controls the percentage of the outer gamut to affect. A value of 0.8 will compress out of gamut values into the outer 20% of the gamut. The inner 80% of the gamut core will not be affected. You can adjust the colors separately: for example if you wanted to protect magenta and yellow a little more than cyan, you could set the threshold a bit higher for cyan.\n\nThe thresholds are relative to the Target Compression Gamut. They may need to be adjusted after changing the target gamut if you want the protected color range to remain unchanged. +TP_COMPRESSGAMUT_DCIP3;DCI-P3 +TP_COMPRESSGAMUT_ACESP1;ACES AP1 +TP_COMPRESSGAMUT_POWER_TOOLTIP;Power: Control the aggressiveness of the compression curve.\n Higher power values result in more compressed values distributed closer to the gamut boundary. This will increase the "color purity" or "saturation" once the image has been rendered through a display transform.\n Higher values have a smoother transition. +TP_COMPRESSGAMUT_LIMIT_TOOLTIP;Maximum Distance Limit: is the distance outside of the gamut boundary to compress to the gamut boundary. For example, a value of 1.2 will compress distance values of 1.2 to a value of 1.0. \nIndividual controls are given for each color component. They are named cyan magenta and yellow because they actually control the max distance from the edge of the triangle (primaries related to the CIExy diagram), not the corner. For example, boosting the value of the cyan distance limit will increase the distance from which values are mapped to the edge between the blue and green corners of the triangle.\nThe distance limits also control the RGB ratios of the compressed values, which affects the apparent hue.\n\nThe distances are relative to the Target Compression Gamut. They may need to be adjusted after changing the target gamut if you want the amount of compression to remain unchanged. +TP_COMPRESSGAMUT_PROPHOTO;ProPhoto +TP_COMPRESSGAMUT_REC2020;Rec. 2020 +TP_COMPRESSGAMUT_ADOBE;Adobe RGB +TP_COMPRESSGAMUT_SRGB;sRGB +TP_COMPRESSGAMUT_CYANTH;Cyan +TP_COMPRESSGAMUT_MAGENTATH;Magenta +TP_COMPRESSGAMUT_YELLOWTH;Yellow +TP_COMPRESSGAMUT_THRESHOLD;Threshold +TP_COMPRESSGAMUT_CYANLIM;Cyan +TP_COMPRESSGAMUT_MAGENTALIM;Magenta +TP_COMPRESSGAMUT_YELLOWLIM;Yellow +TP_COMPRESSGAMUT_LIMIT;Maximum Distance Limits +TP_COMPRESSGAMUT_ROLLOFF;Rolloff +TP_COMPRESSGAMUT_PWR;Power TP_CROP_FIXRATIO;Lock ratio TP_CROP_GTCENTEREDSQUARE;Centered square TP_CROP_GTDIAGONALS;Rule of Diagonals diff --git a/rtengine/color.cc b/rtengine/color.cc index 16104ae86..a9738ac0e 100644 --- a/rtengine/color.cc +++ b/rtengine/color.cc @@ -16,6 +16,10 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include +#include +#include + #include #include "rtengine.h" @@ -26,6 +30,7 @@ #include "iccstore.h" #include #include "linalgebra.h" +#include "procparams.h" using namespace std; @@ -2105,6 +2110,181 @@ float Color::eval_ACEScct_curve(float x, bool forward) // end code take in ART thanks to Alberto Griggio +//functions needs to use ACES + +// transpose Matrix +void Color::transpose(const Matrix &ma, Matrix &R) +{ + if (&ma == &R) { + std::swap(R[0][1], R[1][0]); + std::swap(R[0][2], R[2][0]); + std::swap(R[1][0], R[0][1]); + std::swap(R[1][2], R[2][1]); + std::swap(R[2][0], R[0][2]); + std::swap(R[2][1], R[1][2]); + } else { + R[0][0] = ma[0][0]; + R[0][1] = ma[1][0]; + R[0][2] = ma[2][0]; + R[1][0] = ma[0][1]; + R[1][1] = ma[1][1]; + R[1][2] = ma[2][1]; + R[2][0] = ma[0][2]; + R[2][1] = ma[1][2]; + R[2][2] = ma[2][2]; + } +} + +// multiply Matrix x Matrix +void Color::multip(const Matrix &ma, const Matrix &mb, Matrix &R) +{ + const bool overwrite = &ma == &R || &mb == &R; + if (overwrite) { + // Use buffer to hold result so the input doesn't get overwritten while + // the multiplication is happening. + Matrix buf; + multip(ma, mb, buf); + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + R[i][j] = buf[i][j]; + } + } + } else { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + double sum = 0.0; + for (int k = 0; k < 3; ++k) { + sum += ma[i][k] * mb[k][j]; + } + R[i][j] = sum; + } + } + } +} + +//multiply Matrix +void Color::mult3(std::array &in, const Matrix &ma, std::array &out) +{ + // Use buffer for result in case in and out overlap. + std::array buf{0.f, 0.f, 0.f}; + for (int i = 0; i < 3; ++i) { + for( int j = 0; j < 3; ++j){ + buf[i] += static_cast(in[j] * ma[j][i]); + } + } + std::copy(buf.cbegin(), buf.cend(), out.begin()); +} + +// ACES-style gamut compression +// +// tweaked from the original from https://github.com/jedypod/gamut-compress +// tweaked from CTL in ART thanks to Alberto Griggio + +//from ACES https://docs.acescentral.com/specifications/rgc/#appendix-c-illustrations +// https://docs.acescentral.com/specifications/rgc/#appendix-d-ctl-reference-implementation +// https://docs.acescentral.com/specifications/rgc/ +// Distance from achromatic which will be compressed to the gamut boundary +// Values calculated to encompass the encoding gamuts of common digital cinema cameras +//const float LIM_CYAN = 1.147; +//const float LIM_MAGENTA = 1.264; +//const float LIM_YELLOW = 1.312; + +//Percentage of the core gamut to protect +// Values calculated to protect all the colors of the ColorChecker Classic 24 as given by +// ISO 17321-1 and Ohta (1997) +//const float THR_CYAN = 0.815; +//const float THR_MAGENTA = 0.803; +//const float THR_YELLOW = 0.880; + +// Aggressiveness of the compression curve +//const float PWR = 1.2; +//https://www.gujinwei.org/research/camspec/ + +void Color::aces_reference_gamut_compression( + const std::array &rgb_in, + const std::array &threshold, + const std::array &distance_limit, + const Matrix &to_out, const Matrix &from_out, + float pwr, bool rolloff, + float &R, float &G, float &B) +{ + std::array rgb{rgb_in[0], rgb_in[1], rgb_in[2]}; + + // Calculate scale so compression function passes through distance limit: + // (x=distance_limit, y=1) + std::array s; + for (unsigned i = 0; i < s.size(); ++i) { + // Scale factor: c = (1 - t) / sqrt(l - 1) + s[i] = (1.0f - threshold[i]) / sqrt(fmax(1.001f, distance_limit[i]) - 1.0f); + } + // target colorspace + Color::mult3(rgb, to_out, rgb); + + // Achromatic axis + const float ac = fmax(rgb[0], fmax(rgb[1], rgb[2])); + + // Inverse RGB Ratios: distance from achromatic axis + std::array d{0.f, 0.f, 0.f}; + if (ac != 0) { + for (unsigned i = 0; i < d.size(); ++i) { + d[i] = (ac - rgb[i]) / fabs(ac); + } + } + std::array cd{d[0], d[1], d[2]}; // Compressed distance + if (!rolloff) { + // Parabolic compression function: + // https://www.desmos.com/calculator/nvhp63hmtj + // y = { x < t: x + // x >= t: c sqrt(x - t + c^2 / 4) - c sqrt(c^2 / 4) + t } + // The second piece is equal to + // c (sqrt(x - t + c^2 / 4) - |c / 2|) + t + for (unsigned i = 0; i < cd.size(); ++i) { + if (d[i] >= threshold[i]) { + const float c_2 = s[i] / 2.f; + const float c2_4 = c_2 * c_2; + cd[i] = s[i] * (sqrt(d[i] - threshold[i] + c2_4) - fabs(c_2)) + + threshold[i]; + } + } + } else { + for (unsigned i = 0; i < cd.size(); ++i) { + if (d[i] >= threshold[i]) { + if (threshold[i] == 1.f) { + cd[i] = 1.f; + } else { + // Calculate scale factor for y = 1 intersect + const float limit = distance_limit[i]; + const float thres = threshold[i]; + // l - t + // Scale s = -------------------------- + // ( ( 1 - t )-p )(1 / p) + // ( ( ----- ) - 1 ) + // ( ( l - t ) ) + const float scale = (limit - thres) / pow(pow((1.0f - thres) / (limit - thres), - pwr) - 1.0f, 1.0f / pwr); + // Normalize distance outside threshold by scale factor + // x' = (x - t) / s + const float nd = (d[i] - thres) / scale; + // x' + // y = t + s ---------------- + // (1 + x'^p)^(1/p) + const float po = pow(nd, pwr); + cd[i] = thres + scale * nd / (pow(1.0f + po, 1.0f / pwr)); + } + } + } + } + // Inverse RGB Ratios to RGB + + for (unsigned i = 0; i < rgb.size(); ++i) { + rgb[i] = ac - cd[i] * fabs(ac); + } + //working colorspace from_out + Color::mult3(rgb, from_out, rgb); + R = rgb[0]; + G = rgb[1]; + B = rgb[2]; +} + void Color::primaries_to_xyz(double p[6], double Wx, double Wz, double *pxyz, int cat) { diff --git a/rtengine/color.h b/rtengine/color.h index c20ac97ee..32c828a9c 100644 --- a/rtengine/color.h +++ b/rtengine/color.h @@ -108,6 +108,10 @@ public: ID_DOWN /// Interpolate color by decreasing the hue value, crossing the lower limit } eInterpolationDirection; + using Triple = std::array; + + using Matrix = std::array; + // Wikipedia sRGB: Unlike most other RGB color spaces, the sRGB gamma cannot be expressed as a single numerical value. // The overall gamma is approximately 2.2, consisting of a linear (gamma 1.0) section near black, and a non-linear section elsewhere involving a 2.4 exponent // and a gamma (slope of log output versus log input) changing from 1.0 through about 2.3. @@ -1884,6 +1888,22 @@ static inline void Lab2XYZ(vfloat L, vfloat a, vfloat b, vfloat &x, vfloat &y, v */ static void primaries_to_xyz (double p[6], double Wx, double Wz, double *pxyz, int cat); + /** + * @brief Gamut compress from ACES + */ + // transpose, multip, mult3 used by ACES + static void transpose(const Matrix &ma, Matrix &R); + static void multip(const Matrix &ma, const Matrix &mb, Matrix &R); + static void mult3(std::array &in, const Matrix &ma, std::array &out); + + static void aces_reference_gamut_compression( + const std::array &rgb_in, + const std::array &threshold, + const std::array &distance_limit, + const Matrix &to_out, const Matrix &from_out, + float pwr, bool rolloff, + float &R, float &G, float &B); + /** * @brief Get HSV's hue from the Lab's hue * @param HH Lab's hue value, in radians [-PI ; +PI] diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index bbb2d4bfa..1eb6c7969 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -692,6 +692,10 @@ void Crop::update(int todo) parent->ipf.filmNegativeProcess(baseCrop, baseCrop, params.filmNegative); } + if (params.cg.enabled) {//gamut compression + parent->ipf.gamutcompr(baseCrop, baseCrop); + } + delete [] min_r; delete [] min_b; delete [] lumL; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 4949699fe..31d2f6621 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -835,9 +835,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) imgsrc->convertColorSpace(orig_prev, params->icm, currWB); } + if (params->cg.enabled) {//gamut compression + ipf.gamutcompr(orig_prev, orig_prev); + } + ipf.firstAnalysis(orig_prev, *params, vhist16); } - + oprevi = orig_prev; if ((todo & M_SPOT) && !spotsDone) { diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index db07fd91f..6b15ac4d7 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -93,6 +93,7 @@ struct FattalToneMappingParams; struct ColorManagementParams; struct DirPyrDenoiseParams; struct FilmNegativeParams; +struct CGParams; struct LocalContrastParams; struct LocallabParams; struct SharpeningParams; @@ -513,7 +514,9 @@ enum class BlurType { static void rgb2lab(std::uint8_t red, std::uint8_t green, std::uint8_t blue, float &L, float &a, float &b, const procparams::ColorManagementParams &icm, bool consider_histogram_settings = true); Imagefloat* lab2rgbOut(LabImage* lab, int cx, int cy, int cw, int ch, const procparams::ColorManagementParams &icm); // CieImage *ciec; - void workingtrc(int sp, Imagefloat* src, Imagefloat* dst, int cw, int ch, int mul, Glib::ustring &profile, double gampos, double slpos, int cat, int &illum, int prim, int locprim, + + void gamutcompr( rtengine::Imagefloat *src, rtengine::Imagefloat *dst) const; + void workingtrc(int sp, Imagefloat* src, Imagefloat* dst, int cw, int ch , int mul, Glib::ustring &profile, double gampos, double slpos, int cat, int &illum, int prim, int locprim, float &rdx, float &rdy, float &grx, float &gry, float &blx, float &bly, float &meanx, float &meany, float &meanxe, float &meanye, cmsHTRANSFORM &transform, bool normalizeIn = true, bool normalizeOut = true, bool keepTransForm = false, bool gamutcontrol = false) const; void preserv(LabImage *nprevl, LabImage *provis, int cw, int ch); diff --git a/rtengine/iplab2rgb.cc b/rtengine/iplab2rgb.cc index d2b2c9f2c..cc88b4d46 100644 --- a/rtengine/iplab2rgb.cc +++ b/rtengine/iplab2rgb.cc @@ -423,6 +423,193 @@ void ImProcFunctions::preserv(LabImage *nprevl, LabImage *provis, int cw, int ch } } + + +// ACES-style gamut compression +// +// tweaked from the original from https://github.com/jedypod/gamut-compress +// tweaked from CTL in ART thanks to Alberto Griggio + +//from ACES https://docs.acescentral.com/specifications/rgc/#appendix-c-illustrations +// https://docs.acescentral.com/specifications/rgc/#appendix-d-ctl-reference-implementation +// https://docs.acescentral.com/specifications/rgc/ +// Distance from achromatic which will be compressed to the gamut boundary +// Values calculated to encompass the encoding gamuts of common digital cinema cameras +//const float LIM_CYAN = 1.147; +//const float LIM_MAGENTA = 1.264; +//const float LIM_YELLOW = 1.312; + +//Percentage of the core gamut to protect +// Values calculated to protect all the colors of the ColorChecker Classic 24 as given by +// ISO 17321-1 and Ohta (1997) +//const float THR_CYAN = 0.815; +//const float THR_MAGENTA = 0.803; +//const float THR_YELLOW = 0.880; + +// Aggressiveness of the compression curve +//const float PWR = 1.2; + + +void ImProcFunctions::gamutcompr( Imagefloat *src, Imagefloat *dst) const +{ + if (settings->verbose) { + printf("Apply compression gamut \n"); + } + + using Triple = std::array; + + using Matrix = std::array; + + const TMatrix wprof = ICCStore::getInstance()->workingSpaceMatrix(params->icm.workingProfile); + + Matrix wpro = {}; //working profile set in Matrix format + for (int r = 0; r < 3; ++r) { + for (int c = 0; c < 3; ++c) { + wpro[r][c] = wprof[r][c]; + } + } + //dcip3 Rec2020, srgb, prophoto, acesp1 - Compression gamut matrix profile + Matrix dcip3 = {}; + //in fact the white point is "special" - 0.314 - 0.351 Theater + dcip3[0][0] = 0.4861607;//0.4451698;(original) //0.4861607 with chromatic adaptation D63 => D50 + dcip3[0][1] = 0.3238514;//0.2771344;(original)//0.3238514 with chromatic adaptation D63 => D50 + dcip3[0][2] = 0.1541879;//0.1722827;(original//0.1541879 with chromatic adaptation D63 => D50 + dcip3[1][0] = 0.2266839;//0.2094917;(original//0.2266839 with chromatic adaptation D63 => D50 + dcip3[1][1] = 0.7103336;//0.7215953;(original//0.7103336 with chromatic adaptation D63 => D50 + dcip3[1][2] = 0.0629826;//0.0689131;(original// 0.0629826 with chromatic adaptation D63 => D50 + dcip3[2][0] = -0.0008016;//0.0;(original//-0.0008016 with chromatic adaptation D63 => D50 + dcip3[2][1] = 0.0432353;//0.0470606;(original// 0.0432353 with chromatic adaptation D63 => D50 + dcip3[2][2] = 0.7824663;//0.9073554;(original// 0.7824663 with chromatic adaptation D63 => D50 + + Matrix Rec2020 = {}; + Rec2020[0][0] = 0.6734241; + Rec2020[0][1] = 0.1656411; + Rec2020[0][2] = 0.1251286; + Rec2020[1][0] = 0.2790177; + Rec2020[1][1] = 0.6753402; + Rec2020[1][2] = 0.0456377; + Rec2020[2][0] = -0.0019300; + Rec2020[2][1] = 0.0299784; + Rec2020[2][2] = 0.7973330; + + Matrix srgb = {}; + srgb[0][0] = 0.4360747; + srgb[0][1] = 0.3850649; + srgb[0][2] = 0.1430804; + srgb[1][0] = 0.2225045; + srgb[1][1] = 0.7168786; + srgb[1][2] = 0.0606169; + srgb[2][0] = 0.0139322; + srgb[2][1] = 0.0971045; + srgb[2][2] = 0.7141733; + + Matrix adobe = {}; + adobe[0][0] = 0.6097559; + adobe[0][1] = 0.2052401; + adobe[0][2] = 0.1492240; + adobe[1][0] = 0.3111242; + adobe[1][1] = 0.6256560; + adobe[1][2] = 0.0632197; + adobe[2][0] = 0.0194811; + adobe[2][1] = 0.0608902; + adobe[2][2] = 0.7448387; + + Matrix prophoto = {};//prophoto + prophoto[0][0] = 0.7976749; + prophoto[0][1] = 0.1351917; + prophoto[0][2] = 0.0313534; + prophoto[1][0] = 0.2880402; + prophoto[1][1] = 0.7118741; + prophoto[1][2] = 0.0000857; + prophoto[2][0] = 0.0; + prophoto[2][1] = 0.0; + prophoto[2][2] = 1.2118128; + + Matrix acesp1 = {};//aces P1 + acesp1[0][0] = 0.689697; + acesp1[0][1] = 0.149944; + acesp1[0][2] = 0.124559; + acesp1[1][0] = 0.284448; + acesp1[1][1] = 0.671758; + acesp1[1][2] = 0.043794; + acesp1[2][0] = -0.006043; + acesp1[2][1] = 0.009998; + acesp1[2][2] = 0.820945; + + Matrix out = {}; + + if (params->cg.colorspace == "rec2020") { + out = Rec2020; + } else if (params->cg.colorspace == "prophoto") { + out = prophoto; + } else if (params->cg.colorspace == "adobe") { + out = adobe; + } else if (params->cg.colorspace == "srgb") { + out = srgb; + } else if (params->cg.colorspace == "dcip3") { + out = dcip3; + } else if (params->cg.colorspace == "acesp1") { + out = acesp1; + } else { + out = acesp1; // Should never happen, but just in case. + } + + Matrix inv_out = {}; + if (!rtengine::invertMatrix(out, inv_out)) {//invert matrix + printf("Matrix is not invertible, skipping\n"); + } + + Matrix Rprov = {}; + Color::multip(inv_out, wpro, Rprov);//multiply matrix + Matrix to_out = {}; + + Color::transpose(Rprov, to_out);//transpose Matrix for output + + Matrix from_out = {};//inverse to output + if (!rtengine::invertMatrix(to_out, from_out)) { + printf("Matrix is not invertible, skipping\n"); + + } + + //parameters from GUI + const auto thc = static_cast(params->cg.th_c); + const auto thm = static_cast(params->cg.th_m); + const auto thy = static_cast(params->cg.th_y); + const auto dc = static_cast(params->cg.d_c); + const auto dm = static_cast(params->cg.d_m); + const auto dy = static_cast(params->cg.d_y); + const auto pw = static_cast(params->cg.pwr); + const bool roll = params->cg.rolloff; + + const std::array th{thc, thm, thy};//set parameter GUI in th + const std::array dl{dc, dm, dy};//set parameter GUI in dl + + const int height = src->getHeight(); + const int width = src->getWidth(); + + constexpr float range = 65535.f; + +#ifdef _OPENMP + # pragma omp parallel for schedule(dynamic,16) if (multiThread) +#endif + + for (int i = 0; i < height; ++i) + for (int j = 0; j < width; ++j) { + const float r = src->r(i, j) / range;//in interval 0.. 1 + const float g = src->g(i, j) / range; + const float b = src->b(i, j) / range; + std::array rgb_in{r, g, b}; + float rout = 0.f; + float gout = 0.f; + float bout = 0.f; + Color::aces_reference_gamut_compression(rgb_in, th, dl, to_out, from_out, pw, roll, rout, gout, bout); + dst->r(i, j) = range * rout;//in interval 0..65535 + dst->g(i, j) = range * gout; + dst->b(i, j) = range * bout; + } +} + + void ImProcFunctions::workingtrc(int sp, Imagefloat* src, Imagefloat* dst, int cw, int ch, int mul, Glib::ustring &profile, double gampos, double slpos, int cat, int &illum, int prim, int locprim, float &rdx, float &rdy, float &grx, float &gry, float &blx, float &bly, float &meanx, float &meany, float &meanxe, float &meanye, cmsHTRANSFORM &transform, bool normalizeIn, bool normalizeOut, bool keepTransForm, bool gamutcontrol) const diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 9398af56b..63882d816 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1884,6 +1884,46 @@ bool SHParams::operator !=(const SHParams& other) const return !(*this == other); } + + +CGParams::CGParams() : + enabled(false), + th_c(0.815), + th_m(0.803), + th_y(0.880), + d_c(1.147), + d_m(1.264), + d_y(1.312), + pwr(1.2), + colorspace("acesp1"), + rolloff(true) + +{ +} + +bool CGParams::operator ==(const CGParams& other) const +{ + return + enabled == other.enabled + && th_c == other.th_c + && th_m == other.th_m + && th_y == other.th_y + && d_c == other.d_c + && d_m == other.d_m + && d_y == other.d_y + && pwr == other.pwr + && colorspace == other.colorspace + && rolloff == other.rolloff; +} + +bool CGParams::operator !=(const CGParams& other) const +{ + return !(*this == other); +} + + + +/// ToneEqualizerParams::ToneEqualizerParams() : enabled(false), bands{0, 0, 0, 0, 0, 0}, @@ -6719,6 +6759,18 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->sh.radius, "Shadows & Highlights", "Radius", sh.radius, keyFile); saveToKeyfile(!pedited || pedited->sh.lab, "Shadows & Highlights", "Lab", sh.lab, keyFile); +//compression gamut + saveToKeyfile(!pedited || pedited->cg.enabled, "Compression gamut", "Enabled", cg.enabled, keyFile); + saveToKeyfile(!pedited || pedited->cg.th_c, "Compression gamut", "th_c", cg.th_c, keyFile); + saveToKeyfile(!pedited || pedited->cg.th_m, "Compression gamut", "th_m", cg.th_m, keyFile); + saveToKeyfile(!pedited || pedited->cg.th_y, "Compression gamut", "th_y", cg.th_y, keyFile); + saveToKeyfile(!pedited || pedited->cg.d_c, "Compression gamut", "d_c", cg.d_c, keyFile); + saveToKeyfile(!pedited || pedited->cg.d_m, "Compression gamut", "d_m", cg.d_m, keyFile); + saveToKeyfile(!pedited || pedited->cg.d_y, "Compression gamut", "d_y", cg.d_y, keyFile); + saveToKeyfile(!pedited || pedited->cg.pwr, "Compression gamut", "pwr", cg.pwr, keyFile); + saveToKeyfile(!pedited || pedited->cg.colorspace, "Compression gamut", "colorspace", cg.colorspace, keyFile); + saveToKeyfile(!pedited || pedited->cg.rolloff, "Compression gamut", "rolloff", cg.rolloff, keyFile); + // Tone equalizer saveToKeyfile(!pedited || pedited->toneEqualizer.enabled, "ToneEqualizer", "Enabled", toneEqualizer.enabled, keyFile); for (size_t i = 0; i < toneEqualizer.bands.size(); ++i) { @@ -8859,6 +8911,19 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "FattalToneMapping", "Anchor", fattal.anchor, pedited->fattal.anchor); } + if (keyFile.has_group("Compression gamut")) { + assignFromKeyfile(keyFile, "Compression gamut", "Enabled", cg.enabled, pedited->cg.enabled); + assignFromKeyfile(keyFile, "Compression gamut", "th_c", cg.th_c, pedited->cg.th_c); + assignFromKeyfile(keyFile, "Compression gamut", "th_m", cg.th_m, pedited->cg.th_m); + assignFromKeyfile(keyFile, "Compression gamut", "th_y", cg.th_y, pedited->cg.th_y); + assignFromKeyfile(keyFile, "Compression gamut", "d_c", cg.d_c, pedited->cg.d_c); + assignFromKeyfile(keyFile, "Compression gamut", "d_m", cg.d_m, pedited->cg.d_m); + assignFromKeyfile(keyFile, "Compression gamut", "d_y", cg.d_y, pedited->cg.d_y); + assignFromKeyfile(keyFile, "Compression gamut", "pwr", cg.pwr, pedited->cg.pwr); + assignFromKeyfile(keyFile, "Compression gamut", "colorspace", cg.colorspace, pedited->cg.colorspace); + assignFromKeyfile(keyFile, "Compression gamut", "rolloff", cg.rolloff, pedited->cg.rolloff); + } + if (keyFile.has_group("Shadows & Highlights") && ppVersion >= 333) { assignFromKeyfile(keyFile, "Shadows & Highlights", "Enabled", sh.enabled, pedited->sh.enabled); assignFromKeyfile(keyFile, "Shadows & Highlights", "Highlights", sh.highlights, pedited->sh.highlights); diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 117c8b3d6..80a9100d1 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -858,6 +858,28 @@ struct SHParams { bool operator !=(const SHParams& other) const; }; +/** + * Parameters of the compression gamut + */ +struct CGParams { + bool enabled; + double th_c; + double th_m; + double th_y; + double d_c; + double d_m; + double d_y; + double pwr; + Glib::ustring colorspace; + bool rolloff; + + CGParams(); + + bool operator ==(const CGParams& other) const; + bool operator !=(const CGParams& other) const; +}; + + /** * Tone equalizer parameters. */ @@ -2725,6 +2747,7 @@ public: EPDParams epd; ///< Edge Preserving Decomposition parameters FattalToneMappingParams fattal; ///< Fattal02 tone mapping SHParams sh; ///< Shadow/highlight enhancement parameters + CGParams cg; ///< Compression gamut ToneEqualizerParams toneEqualizer; ///< Tone equalizer parameters CropParams crop; ///< Crop parameters CoarseTransformParams coarse; ///< Coarse transformation (90, 180, 270 deg rotation, h/v flipping) parameters diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h index 7113ba6a5..00bf8e11b 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -59,6 +59,7 @@ #define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define DEMOSAIC (M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define WB (M_WB|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define COMPR (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_MONITOR) #define ALLNORAW (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define CAPTURESHARPEN (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_CSHARP) #define HDR (M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 26c1cd646..4448a149a 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -1327,6 +1327,8 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT ipf.filmNegativeProcess(baseImg, baseImg, params.filmNegative); } + + LUTu hist16 (65536); ipf.firstAnalysis (baseImg, params, hist16); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 4850863f8..fa15285a7 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -969,8 +969,13 @@ private: // perform first analysis hist16(65536); + if (params.cg.enabled) {//gamut compression + ipf.gamutcompr(baseImg, baseImg); + } + ipf.firstAnalysis(baseImg, params, hist16); + ipf.dehaze(baseImg, params.dehaze); ipf.ToneMapFattal02(baseImg, params.fattal, 3, 0, nullptr, 0, 0, 0, false); diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index 592b816a0..cd2b01895 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -37,6 +37,7 @@ set(NONCLISOURCEFILES controllines.cc controlspotpanel.cc coordinateadjuster.cc + compressgamut.cc crop.cc crophandler.cc cropwindow.cc diff --git a/rtgui/compressgamut.cc b/rtgui/compressgamut.cc new file mode 100644 index 000000000..c18f89970 --- /dev/null +++ b/rtgui/compressgamut.cc @@ -0,0 +1,365 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2024 RawTherapee team + * + * 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 . + */ + /* + * tweaked from the original from https://github.com/jedypod/gamut-compress + * https://docs.acescentral.com/specifications/rgc/ +*/ +#include "compressgamut.h" + +#include "eventmapper.h" + +#include "../rtengine/procparams.h" + +using namespace rtengine; +using namespace rtengine::procparams; + +const Glib::ustring Compressgamut::TOOL_NAME = "compressgamut"; + +Compressgamut::Compressgamut () : FoldableToolPanel(this, TOOL_NAME, M("TP_COMPRESSGAMUT_LABEL"), false, true) +{ + auto m = ProcEventMapper::getInstance(); + EvcgColorspace = m->newEvent(COMPR, "HISTORY_MSG_CG_COLORSPACE");//FIRST to reinit histogram + Evcgthc = m->newEvent(COMPR, "HISTORY_MSG_CG_CYANTH"); + Evcgthm = m->newEvent(COMPR, "HISTORY_MSG_CG_MAGENTATH"); + Evcgthy = m->newEvent(COMPR, "HISTORY_MSG_CG_YELLOWTH"); + Evcgdc = m->newEvent(COMPR, "HISTORY_MSG_CG_CYANLIM"); + Evcgdm = m->newEvent(COMPR, "HISTORY_MSG_CG_MAGENTALIM"); + Evcgdy = m->newEvent(COMPR, "HISTORY_MSG_CG_YELLOWLIM"); + Evcgroll = m->newEvent(COMPR, "HISTORY_MSG_CG_ROLLOFF"); + Evcgpwr = m->newEvent(COMPR, "HISTORY_MSG_CG_VALUE"); + Evcgenabled = m->newEvent(COMPR, "HISTORY_MSG_CG_ENABLED"); + + + + Gtk::Frame *iFrame = Gtk::manage(new Gtk::Frame(M("TP_COMPRESSGAMUT_MAIN_COLORSPACE"))); + + iFrame->set_label_align(0.025f, 0.5); + iFrame->set_tooltip_markup (M("TP_COMPRESSGAMUT_COLORSPACE_TOOLTIP")); + + Gtk::Box *iVBox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + colorspace = Gtk::manage(new MyComboBoxText()); + colorspace->append(M("TP_COMPRESSGAMUT_REC2020")); + colorspace->append(M("TP_COMPRESSGAMUT_PROPHOTO")); + colorspace->append(M("TP_COMPRESSGAMUT_ADOBE")); + colorspace->append(M("TP_COMPRESSGAMUT_SRGB")); + colorspace->append(M("TP_COMPRESSGAMUT_DCIP3")); + colorspace->append(M("TP_COMPRESSGAMUT_ACESP1")); + colorspace->set_active(3); + iVBox->pack_start(*colorspace); + iFrame->add(*iVBox); + pack_start(*iFrame); + colorspaceconn = colorspace->signal_changed().connect(sigc::mem_fun(*this, &Compressgamut::colorspaceChanged)); + + // Percentage of the core gamut to protect Limits + // Values calculated to protect all the colors of the ColorChecker Classic 24 as given by + // ISO 17321-1 and Ohta (1997) + + th_c = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_CYANTH"), 0., 1., 0.001, 0.815));//0.25 sRGB 0.999 to avoid: 1 - th = 0 + th_m = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_MAGENTATH"), 0., 1., 0.001, 0.803));//0.925 sRGB + th_y = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_YELLOWTH"), 0., 1., 0.001, 0.880));//0.934 sRGB + + Gtk::Frame *thFrame = Gtk::manage(new Gtk::Frame(M("TP_COMPRESSGAMUT_THRESHOLD"))); + thFrame->set_label_align(0.025f, 0.5); + Gtk::Box *thVBox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + thFrame->set_tooltip_markup (M("TP_COMPRESSGAMUT_THRESHOLD_TOOLTIP")); + + thVBox->pack_start (*th_c); + thVBox->pack_start (*th_m); + thVBox->pack_start (*th_y); + thFrame->add(*thVBox); + pack_start(*thFrame, Gtk::PACK_SHRINK); + // from ACES https://docs.acescentral.com/specifications/rgc/#appendix-c-illustrations + // https://docs.acescentral.com/specifications/rgc/#appendix-d-ctl-reference-implementation + // Distance from achromatic which will be compressed to the gamut boundary + // Values calculated to encompass the encoding gamuts of common digital cinema cameras + d_c = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_CYANLIM"), 1.001, 2.0, 0.001, 1.147));//1.05 sRGB + d_m = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_MAGENTALIM"), 1.001, 2.0, 0.001, 1.264));//1.08 sRGB + d_y = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_YELLOWLIM"), 1.001, 2.0, 0.001, 1.312));//1.10 sRGB + + Gtk::Frame *limFrame = Gtk::manage(new Gtk::Frame(M("TP_COMPRESSGAMUT_LIMIT"))); + limFrame->set_label_align(0.025f, 0.5); + limFrame->set_tooltip_markup (M("TP_COMPRESSGAMUT_LIMIT_TOOLTIP")); + + Gtk::Box *limVBox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + limVBox->pack_start (*d_c); + limVBox->pack_start (*d_m); + limVBox->pack_start (*d_y); + limFrame->add(*limVBox); + pack_start(*limFrame, Gtk::PACK_SHRINK); + + // Aggressiveness of the compression curve Pwr + rolloff = Gtk::manage(new Gtk::CheckButton(M("TP_COMPRESSGAMUT_ROLLOFF"))); + pwr = Gtk::manage (new Adjuster (M("TP_COMPRESSGAMUT_PWR"), 0.5, 2.0, 0.01, 1.2)); + rolloffconn = rolloff->signal_toggled().connect (sigc::mem_fun (*this, &Compressgamut::rolloff_change)); + + Gtk::Frame *rollFrame = Gtk::manage(new Gtk::Frame()); + rollFrame->set_label_align(0.025f, 0.5); + rollFrame->set_label_widget(*rolloff); + rolloff->set_active(true); + Gtk::Box *rollVBox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + rollFrame->set_tooltip_markup (M("TP_COMPRESSGAMUT_POWER_TOOLTIP")); + + rollVBox->pack_start (*pwr); + rollFrame->add(*rollVBox); + pack_start(*rollFrame, Gtk::PACK_SHRINK); + th_c->setAdjusterListener (this); + th_m->setAdjusterListener (this); + th_y->setAdjusterListener (this); + d_c->setAdjusterListener (this); + d_m->setAdjusterListener (this); + d_y->setAdjusterListener (this); + pwr->setAdjusterListener (this); + + show_all_children (); +} + +void Compressgamut::read (const ProcParams* pp, const ParamsEdited* pedited) +{ + + disableListener (); + + if (pedited) { + th_c->setEditedState (pedited->cg.th_c ? Edited : UnEdited); + th_m->setEditedState (pedited->cg.th_m ? Edited : UnEdited); + th_y->setEditedState (pedited->cg.th_y ? Edited : UnEdited); + d_c->setEditedState (pedited->cg.d_c ? Edited : UnEdited); + d_m->setEditedState (pedited->cg.d_m ? Edited : UnEdited); + d_y->setEditedState (pedited->cg.d_y ? Edited : UnEdited); + pwr->setEditedState (pedited->cg.pwr ? Edited : UnEdited); + set_inconsistent (multiImage && !pedited->cg.enabled); + rolloff->set_inconsistent (!pedited->cg.rolloff); + } + + setEnabled (pp->cg.enabled); + + rolloffconn.block (true); + rolloff->set_active (pp->cg.rolloff); + rolloffconn.block (false); + th_c->setValue(pp->cg.th_c); + th_m->setValue(pp->cg.th_m); + th_y->setValue(pp->cg.th_y); + d_c->setValue(pp->cg.d_c); + d_m->setValue(pp->cg.d_m); + d_y->setValue(pp->cg.d_y); + pwr->setValue(pp->cg.pwr); + + colorspaceconn.block (true); + + if (pp->cg.colorspace == "rec2020") { + colorspace->set_active(0); + } else if (pp->cg.colorspace == "prophoto") { + colorspace->set_active(1); + } else if (pp->cg.colorspace == "adobe") { + colorspace->set_active(2); + } else if (pp->cg.colorspace == "srgb") { + colorspace->set_active(3); + } else if (pp->cg.colorspace == "dcip3") { + colorspace->set_active(4); + } else if (pp->cg.colorspace == "acesp1") { + colorspace->set_active(5); + } + colorspaceconn.block (false); + + rolloffconn.block (true); + rolloff->set_active (pp->cg.rolloff); + rolloffconn.block (false); + + lastrolloff = pp->cg.rolloff; + + rolloff_change(); + colorspaceChanged(); + enabledChanged (); + + enableListener (); +} + +void Compressgamut::write (ProcParams* pp, ParamsEdited* pedited) +{ + + pp->cg.th_c = th_c->getValue (); + pp->cg.th_m = th_m->getValue (); + pp->cg.th_y = th_y->getValue (); + pp->cg.d_c = d_c->getValue (); + pp->cg.d_m = d_m->getValue (); + pp->cg.d_y = d_y->getValue (); + pp->cg.pwr = pwr->getValue (); + pp->cg.rolloff = rolloff->get_active(); + pp->cg.enabled = getEnabled(); + + if (colorspace->get_active_row_number() == 0) { + pp->cg.colorspace = "rec2020"; + } else if (colorspace->get_active_row_number() == 1){ + pp->cg.colorspace = "prophoto"; + } else if (colorspace->get_active_row_number() == 2){ + pp->cg.colorspace = "adobe"; + } else if (colorspace->get_active_row_number() == 3){ + pp->cg.colorspace = "srgb"; + } else if (colorspace->get_active_row_number() == 4){ + pp->cg.colorspace = "dcip3"; + } else if (colorspace->get_active_row_number() == 5){ + pp->cg.colorspace = "acesp1"; + } + + + if (pedited) { + pedited->cg.th_c = th_c->getEditedState (); + pedited->cg.th_m = th_m->getEditedState (); + pedited->cg.th_y = th_y->getEditedState (); + pedited->cg.d_c = d_c->getEditedState (); + pedited->cg.d_m = d_m->getEditedState (); + pedited->cg.d_y = d_y->getEditedState (); + pedited->cg.pwr = pwr->getEditedState (); + pedited->cg.enabled = !get_inconsistent(); + pedited->cg.colorspace = colorspace->get_active_row_number() != 6; + pedited->cg.rolloff = !rolloff->get_inconsistent(); + } + +} + +void Compressgamut::setDefaults (const ProcParams* defParams, const ParamsEdited* pedited) +{ + + th_c->setDefault (defParams->cg.th_c); + th_m->setDefault (defParams->cg.th_m); + th_y->setDefault (defParams->cg.th_y); + d_c->setDefault (defParams->cg.d_c); + d_m->setDefault (defParams->cg.d_m); + d_y->setDefault (defParams->cg.d_y); + pwr->setDefault (defParams->cg.pwr); + + if (pedited) { + th_c->setDefaultEditedState (pedited->cg.th_c ? Edited : UnEdited); + th_m->setDefaultEditedState (pedited->cg.th_m ? Edited : UnEdited); + th_y->setDefaultEditedState (pedited->cg.th_y ? Edited : UnEdited); + d_c->setDefaultEditedState (pedited->cg.d_c ? Edited : UnEdited); + d_m->setDefaultEditedState (pedited->cg.d_m ? Edited : UnEdited); + d_y->setDefaultEditedState (pedited->cg.d_y ? Edited : UnEdited); + pwr->setDefaultEditedState (pedited->cg.pwr ? Edited : UnEdited); + + } else { + th_c->setDefaultEditedState (Irrelevant); + th_m->setDefaultEditedState (Irrelevant); + th_y->setDefaultEditedState (Irrelevant); + d_c->setDefaultEditedState (Irrelevant); + d_m->setDefaultEditedState (Irrelevant); + d_y->setDefaultEditedState (Irrelevant); + pwr->setDefaultEditedState (Irrelevant); + } + +} + +void Compressgamut::adjusterChanged (Adjuster* a, double newval) +{ + + if (listener && getEnabled()) { + const Glib::ustring costr = Glib::ustring::format (a->getValue()); + + if (a == th_c) { + listener->panelChanged (Evcgthc, costr); + } else if (a == th_m) { + listener->panelChanged (Evcgthm, costr); + } else if (a == th_y) { + listener->panelChanged (Evcgthy, costr); + } else if (a == d_c) { + listener->panelChanged (Evcgdc, costr); + } else if (a == d_m) { + listener->panelChanged (Evcgdm, costr); + } else if (a == d_y) { + listener->panelChanged (Evcgdy, costr); + } else if (a == pwr) { + listener->panelChanged (Evcgpwr, costr); + } + } + +} + +void Compressgamut::rolloff_change() +{ + if (rolloff->get_active()) { + pwr->set_sensitive(true); + } else { + pwr->set_sensitive(false); + } + + if (batchMode) { + if (rolloff->get_inconsistent()) { + rolloff->set_inconsistent (false); + rolloffconn.block (true); + rolloff->set_active (false); + rolloffconn.block (false); + } else if (lastrolloff) { + rolloff->set_inconsistent (true); + } + + lastrolloff = rolloff->get_active (); + } + + if (listener) { + if (rolloff->get_active()) { + listener->panelChanged(Evcgroll, M("GENERAL_ENABLED")); + } else { + listener->panelChanged(Evcgroll, M("GENERAL_DISABLED")); + } + } +} + +void Compressgamut::colorspaceChanged() +{ + if (listener && getEnabled()) { + listener->panelChanged(EvcgColorspace, colorspace->get_active_text()); + } +} + +void Compressgamut::enabledChanged () +{ + if (listener) { + if (get_inconsistent()) { + listener->panelChanged (Evcgenabled, M("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged (Evcgenabled, M("GENERAL_ENABLED")); + } else { + listener->panelChanged (Evcgenabled, M("GENERAL_DISABLED")); + } + } +} +void Compressgamut::setBatchMode (bool batchMode) +{ + ToolPanel::setBatchMode (batchMode); + colorspace->append (M("GENERAL_UNCHANGED")); + th_c->showEditedCB (); + th_m->showEditedCB (); + th_y->showEditedCB (); + d_c->showEditedCB (); + d_m->showEditedCB (); + d_y->showEditedCB (); + pwr->showEditedCB (); +} + + +void Compressgamut::trimValues (rtengine::procparams::ProcParams* pp) +{ + th_c->trimValue(pp->cg.th_c); + th_m->trimValue(pp->cg.th_m); + th_y->trimValue(pp->cg.th_y); + d_c->trimValue(pp->cg.d_c); + d_m->trimValue(pp->cg.d_m); + d_y->trimValue(pp->cg.d_y); + pwr->trimValue(pp->cg.pwr); +} diff --git a/rtgui/compressgamut.h b/rtgui/compressgamut.h new file mode 100644 index 000000000..c873e0737 --- /dev/null +++ b/rtgui/compressgamut.h @@ -0,0 +1,74 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2024 RawTherapee team + * + * 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 Compressgamut final : + public ToolParamBlock, + public AdjusterListener, + public FoldableToolPanel +{ + +protected: + Adjuster* th_c; + Adjuster* th_m; + Adjuster* th_y; + Adjuster* d_c; + Adjuster* d_m; + Adjuster* d_y; + Adjuster* pwr; + MyComboBoxText *colorspace; + sigc::connection colorspaceconn; + Gtk::CheckButton* rolloff; + sigc::connection rolloffconn; + bool lastrolloff; + + rtengine::ProcEvent EvcgColorspace; + rtengine::ProcEvent Evcgthc; + rtengine::ProcEvent Evcgthm; + rtengine::ProcEvent Evcgthy; + rtengine::ProcEvent Evcgdc; + rtengine::ProcEvent Evcgdm; + rtengine::ProcEvent Evcgdy; + rtengine::ProcEvent Evcgroll; + rtengine::ProcEvent Evcgpwr; + rtengine::ProcEvent Evcgenabled; + +public: + static const Glib::ustring TOOL_NAME; + + Compressgamut (); + + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr) override; + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr) override; + void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited = nullptr) override; + void setBatchMode (bool batchMode) override; + + void adjusterChanged (Adjuster* a, double newval) override; + void enabledChanged () override; + void rolloff_change(); + + void trimValues (rtengine::procparams::ProcParams* pp) override; + + void colorspaceChanged(); +}; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 27f36f46a..7a92194ba 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -326,6 +326,17 @@ void ParamsEdited::set(bool v) sh.stonalwidth = v; sh.radius = v; sh.lab = v; + cg.enabled = v; + cg.th_c = v; + cg.th_m = v; + cg.th_y = v; + cg.d_c = v; + cg.d_m = v; + cg.d_y = v; + cg.pwr = v; + cg.colorspace = v; + cg.rolloff = v; + toneEqualizer.enabled = v; toneEqualizer.bands.fill(v); toneEqualizer.regularization = v; @@ -1061,6 +1072,18 @@ void ParamsEdited::initFrom(const std::vector& 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; + + cg.enabled = cg.enabled && p.cg.enabled == other.cg.enabled; + cg.th_c = cg.th_c && p.cg.th_c == other.cg.th_c; + cg.th_m = cg.th_m && p.cg.th_m == other.cg.th_m; + cg.th_y = cg.th_y && p.cg.th_y == other.cg.th_y; + cg.d_c = cg.d_c && p.cg.d_c == other.cg.d_c; + cg.d_m = cg.d_m && p.cg.d_m == other.cg.d_m; + cg.d_y = cg.d_y && p.cg.d_y == other.cg.d_y; + cg.pwr = cg.pwr && p.cg.pwr == other.cg.pwr; + cg.colorspace = cg.colorspace && p.cg.colorspace == other.cg.colorspace; + cg.rolloff = cg.rolloff && p.cg.rolloff == other.cg.rolloff; + 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; @@ -3365,6 +3388,46 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.sh.lab = mods.sh.lab; } + if (cg.th_c) { + toEdit.cg.th_c = mods.cg.th_c; + } + + if (cg.th_m) { + toEdit.cg.th_m = mods.cg.th_m; + } + + if (cg.th_y) { + toEdit.cg.th_y = mods.cg.th_y; + } + + if (cg.d_c) { + toEdit.cg.d_c = mods.cg.d_c; + } + + if (cg.d_m) { + toEdit.cg.d_m = mods.cg.d_m; + } + + if (cg.d_y) { + toEdit.cg.d_y = mods.cg.d_y; + } + + if (cg.colorspace) { + toEdit.cg.colorspace = mods.cg.colorspace; + } + + if (cg.pwr) { + toEdit.cg.pwr = mods.cg.pwr; + } + + if (cg.rolloff) { + toEdit.cg.rolloff = mods.cg.rolloff; + } + + if (cg.enabled) { + toEdit.cg.enabled = mods.cg.enabled; + } + if (toneEqualizer.enabled) { toEdit.toneEqualizer.enabled = mods.toneEqualizer.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 0293aec69..f3cefe653 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -369,6 +369,19 @@ struct SHParamsEdited { bool lab; }; +struct CGParamsEdited { + bool enabled; + bool th_c; + bool th_m; + bool th_y; + bool d_c; + bool d_m; + bool d_y; + bool pwr; + bool colorspace; + bool rolloff; +}; + struct ToneEqualizerParamsEdited { bool enabled; std::array bands; @@ -1651,6 +1664,8 @@ struct ParamsEdited { FattalToneMappingParamsEdited fattal; ImpulseDenoiseParamsEdited impulseDenoise; SHParamsEdited sh; + CGParamsEdited cg; + ToneEqualizerParamsEdited toneEqualizer; CropParamsEdited crop; CoarseTransformParamsEdited coarse; diff --git a/rtgui/toollocationpref.cc b/rtgui/toollocationpref.cc index b66f74536..4352e1b07 100644 --- a/rtgui/toollocationpref.cc +++ b/rtgui/toollocationpref.cc @@ -75,6 +75,8 @@ Glib::ustring getToolTitleKey(Tool tool) return "TP_IMPULSEDENOISE_LABEL"; case Tool::DEFRINGE_TOOL: return "TP_DEFRINGE_LABEL"; + case Tool::COMPRESSGAMUT_TOOL: + return "TP_COMPRESSGAMUT_LABEL"; case Tool::SPOT: return "TP_SPOT_LABEL"; case Tool::DIR_PYR_DENOISE: diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 9b3bcf6ea..4dd1cca22 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -101,6 +101,9 @@ const std::vector COLOR_PANEL_TOOLS = { { .id = Tool::WHITE_BALANCE, }, + { + .id = Tool::COMPRESSGAMUT_TOOL, + }, { .id = Tool::VIBRANCE, }, @@ -309,6 +312,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit vibrance = Gtk::manage(new Vibrance()); colorappearance = Gtk::manage(new ColorAppearance()); whitebalance = Gtk::manage(new WhiteBalance()); + compressgamut = Gtk::manage (new Compressgamut ()); vignetting = Gtk::manage(new Vignetting()); retinex = Gtk::manage(new Retinex()); gradient = Gtk::manage(new Gradient()); @@ -576,6 +580,8 @@ std::string ToolPanelCoordinator::getToolName(Tool tool) return ImpulseDenoise::TOOL_NAME; case Tool::DEFRINGE_TOOL: return Defringe::TOOL_NAME; + case Tool::COMPRESSGAMUT_TOOL: + return Compressgamut::TOOL_NAME; case Tool::SPOT: return Spot::TOOL_NAME; case Tool::DIR_PYR_DENOISE: @@ -1937,6 +1943,8 @@ FoldableToolPanel *ToolPanelCoordinator::getFoldableToolPanel(Tool tool) const return impulsedenoise; case Tool::DEFRINGE_TOOL: return defringe; + case Tool::COMPRESSGAMUT_TOOL: + return compressgamut; case Tool::SPOT: return spot; case Tool::DIR_PYR_DENOISE: diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index d34c9870d..6801664f6 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -32,6 +32,7 @@ #include "coarsepanel.h" #include "colorappearance.h" #include "colortoning.h" +#include "compressgamut.h" #include "crop.h" #include "darkframe.h" #include "defringe.h" @@ -139,6 +140,7 @@ protected: LocalContrast *localContrast; Spot* spot; Defringe* defringe; + Compressgamut* compressgamut; ImpulseDenoise* impulsedenoise; DirPyrDenoise* dirpyrdenoise; EdgePreservingDecompositionUI *epd; @@ -251,6 +253,7 @@ public: TONE_EQUALIZER, IMPULSE_DENOISE, DEFRINGE_TOOL, + COMPRESSGAMUT_TOOL, SPOT, DIR_PYR_DENOISE, EPD,