diff --git a/rtdata/languages/default b/rtdata/languages/default index e13937a10..418f42288 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1257,8 +1257,10 @@ HISTORY_MSG_DEHAZE_STRENGTH;Dehaze - Strength HISTORY_MSG_DUALDEMOSAIC_AUTO_CONTRAST;Dual demosaic - Auto threshold HISTORY_MSG_DUALDEMOSAIC_CONTRAST;Dual demosaic - Contrast threshold HISTORY_MSG_EDGEFFECT;Edge Attenuation response +HISTORY_MSG_FILMNEGATIVE_BALANCE;FN - Reference output +HISTORY_MSG_FILMNEGATIVE_COLORSPACE;Film negative color space HISTORY_MSG_FILMNEGATIVE_ENABLED;Film Negative -HISTORY_MSG_FILMNEGATIVE_FILMBASE;Film base color +HISTORY_MSG_FILMNEGATIVE_REF_SPOT;FN - Reference input HISTORY_MSG_FILMNEGATIVE_VALUES;Film negative values HISTORY_MSG_HISTMATCHING;Auto-matched tone curve HISTORY_MSG_ICM_OUTPUT_PRIMARIES;Output - Primaries @@ -1559,7 +1561,7 @@ PARTIALPASTE_EQUALIZER;Wavelet levels PARTIALPASTE_EVERYTHING;Everything PARTIALPASTE_EXIFCHANGES;Exif PARTIALPASTE_EXPOSURE;Exposure -PARTIALPASTE_FILMNEGATIVE;Film Negative +PARTIALPASTE_FILMNEGATIVE;Film negative PARTIALPASTE_FILMSIMULATION;Film simulation PARTIALPASTE_FLATFIELDAUTOSELECT;Flat-field auto-selection PARTIALPASTE_FLATFIELDBLURRADIUS;Flat-field blur radius @@ -2252,12 +2254,19 @@ TP_EXPOSURE_TCMODE_WEIGHTEDSTD;Weighted Standard TP_EXPOS_BLACKPOINT_LABEL;Raw Black Points TP_EXPOS_WHITEPOINT_LABEL;Raw White Points TP_FILMNEGATIVE_BLUE;Blue ratio -TP_FILMNEGATIVE_FILMBASE_PICK;Pick film base color -TP_FILMNEGATIVE_FILMBASE_TOOLTIP;Pick a spot of unexposed film (eg. the border between frames), to get the actual film base color values, and save them in the processing profile.\nThis makes it easy to get a more consistent color balance when batch-processing multiple pictures from the same roll.\nAlso use this when the converted image is extremely dark, bright, or color-unbalanced. -TP_FILMNEGATIVE_FILMBASE_VALUES;Film base RGB: -TP_FILMNEGATIVE_GREEN;Reference exponent (contrast) -TP_FILMNEGATIVE_GUESS_TOOLTIP;Automatically set the red and blue ratios by picking two patches which had a neutral hue (no color) in the original scene. The patches should differ in brightness. Set the white balance afterwards. +TP_FILMNEGATIVE_BLUEBALANCE;Cool/Warm +TP_FILMNEGATIVE_COLORSPACE;Inversion color space: +TP_FILMNEGATIVE_COLORSPACE_INPUT;Input color space +TP_FILMNEGATIVE_COLORSPACE_TOOLTIP;Select the color space used to perform the negative inversion:\nInput color space : perform inversion before the input profile is applied, as in the previous versions of RT.\nWorking color space : perform inversion after input profile, using the currently selected working profile. +TP_FILMNEGATIVE_COLORSPACE_WORKING;Working color space +TP_FILMNEGATIVE_REF_LABEL;Input RGB: %1 +TP_FILMNEGATIVE_REF_PICK;Pick white balance spot +TP_FILMNEGATIVE_REF_TOOLTIP;Pick a gray patch for white-balancing the output, positive image. +TP_FILMNEGATIVE_GREEN;Reference exponent +TP_FILMNEGATIVE_GREENBALANCE;Magenta/Green +TP_FILMNEGATIVE_GUESS_TOOLTIP;Automatically set the red and blue ratios by picking two patches which had a neutral hue (no color) in the original scene. The patches should differ in brightness. TP_FILMNEGATIVE_LABEL;Film Negative +TP_FILMNEGATIVE_OUT_LEVEL;Output level TP_FILMNEGATIVE_PICK;Pick neutral spots TP_FILMNEGATIVE_RED;Red ratio TP_FILMSIMULATION_LABEL;Film Simulation diff --git a/rtdata/profiles/Film Negative.pp3 b/rtdata/profiles/Film Negative.pp3 new file mode 100644 index 000000000..c298a6ea9 --- /dev/null +++ b/rtdata/profiles/Film Negative.pp3 @@ -0,0 +1,35 @@ +[Exposure] +Auto=false +Compensation=0 +HistogramMatching=false +CurveFromHistogramMatching=false +ClampOOG=false +CurveMode=Standard +CurveMode2=Standard +Curve=1;0;0;0.88544601940051371;1; +Curve2=1;0;0;0.0397505754145333;0.020171771436200074;0.54669745433149319;0.69419974733677647;1;1; + +[HLRecovery] +Enabled=false +Method=Blend + +[Color Management] +InputProfile=(camera) +ToneCurve=false +ApplyLookTable=false +ApplyHueSatMap=false +WorkingProfile=Rec2020 +WorkingTRC=none + +[RAW Bayer] +Method=rcd + +[Film Negative] +Enabled=true +RedRatio=1.360 +GreenExponent=1.5 +BlueRatio=0.86 +ColorSpace=1 +RefInput=0;0;0; +RefOutput=0;0;0; +BackCompat=0 diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 543cd4841..cb96daa03 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -94,7 +94,6 @@ set(RTENGINESOURCEFILES fast_demo.cc ffmanager.cc filmnegativeproc.cc - filmnegativethumb.cc flatcurves.cc FTblockDN.cc gamutwarning.cc diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index e5eb18330..64e2d6e60 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -669,8 +669,16 @@ void Crop::update(int todo) } } + if (params.filmNegative.enabled && params.filmNegative.colorSpace == FilmNegativeParams::ColorSpace::INPUT) { + parent->ipf.filmNegativeProcess(baseCrop, baseCrop, params.filmNegative); + } + parent->imgsrc->convertColorSpace(origCrop, params.icm, parent->currWB); + if (params.filmNegative.enabled && params.filmNegative.colorSpace != FilmNegativeParams::ColorSpace::INPUT) { + parent->ipf.filmNegativeProcess(baseCrop, baseCrop, params.filmNegative); + } + delete [] min_r; delete [] min_b; delete [] lumL; diff --git a/rtengine/filmnegativeproc.cc b/rtengine/filmnegativeproc.cc index 30d0a052d..ae1813db9 100644 --- a/rtengine/filmnegativeproc.cc +++ b/rtengine/filmnegativeproc.cc @@ -21,550 +21,647 @@ #include "rawimage.h" #include "rawimagesource.h" +#include "improcfun.h" +#include "improccoordinator.h" +#include "imagefloat.h" #include "coord.h" -#include "mytime.h" #include "opthelper.h" #include "pixelsmap.h" #include "procparams.h" #include "rt_algo.h" #include "rtengine.h" +#include "rtthumbnail.h" #include "sleef.h" //#define BENCHMARK -#include "StopWatch.h" +//#include "StopWatch.h" +#include "iccstore.h" +#include "rt_math.h" +#include "color.h" + namespace { -using rtengine::ST_BAYER; -using rtengine::ST_FUJI_XTRANS; using rtengine::settings; +using rtengine::Coord2D; +using rtengine::ColorTemp; +using rtengine::findMinMaxPercentile; +using rtengine::ImageSource; +using rtengine::Imagefloat; +using rtengine::procparams::FilmNegativeParams; +using rtengine::procparams::ColorManagementParams; +using rtengine::procparams::RAWParams; +using rtengine::ICCStore; +using rtengine::MAXVALF; +using rtengine::CLIP; +using rtengine::TMatrix; +using rtengine::Color; +using RGB = rtengine::procparams::FilmNegativeParams::RGB; -bool channelsAvg( - const rtengine::RawImage* ri, - int width, - int height, - const float* cblacksom, - rtengine::Coord spotPos, - int spotSize, - std::array& avgs -) +Coord2D translateCoord(const rtengine::ImProcFunctions& ipf, int fw, int fh, int x, int y) { - avgs = {}; // Channel averages - if (ri->getSensorType() != ST_BAYER && ri->getSensorType() != ST_FUJI_XTRANS) { - return false; - } + const std::vector points = {Coord2D(x, y)}; + + std::vector red; + std::vector green; + std::vector blue; + ipf.transCoord(fw, fh, points, red, green, blue); + + return green[0]; +} + + +void getSpotAvgMax(ImageSource *imgsrc, ColorTemp currWB, const std::unique_ptr ¶ms, + Coord2D p, int tr, int spotSize, RGB &avg, RGB &max) +{ + int x1 = MAX(0, (int)p.x - spotSize / 2); + int y1 = MAX(0, (int)p.y - spotSize / 2); + PreviewProps pp(x1, y1, spotSize, spotSize, 1); if (settings->verbose) { - printf("Spot coord: x=%d y=%d\n", spotPos.x, spotPos.y); + printf("Spot: %d,%d %d,%d\n", x1, y1, x1 + spotSize / 2, y1 + spotSize / 2); } - const int half_spot_size = spotSize / 2; + rtengine::Imagefloat spotImg(spotSize, spotSize); + imgsrc->getImage(currWB, tr, &spotImg, pp, params->toneCurve, params->raw); - const int& x1 = spotPos.x - half_spot_size; - const int& x2 = spotPos.x + half_spot_size; - const int& y1 = spotPos.y - half_spot_size; - const int& y2 = spotPos.y + half_spot_size; + auto avgMax = [spotSize, &spotImg](RGB & avg, RGB & max) -> void { + avg = {}; + max = {}; - if (x1 < 0 || x2 > width || y1 < 0 || y2 > height) { - return false; // Spot goes outside bounds, bail out. - } + for (int i = 0; i < spotSize; ++i) + { + for (int j = 0; j < spotSize; ++j) { - std::array pxCount = {}; // Per-channel sample counts + float r = spotImg.r(i, j); + float g = spotImg.g(i, j); + float b = spotImg.b(i, j); - for (int c = x1; c < x2; ++c) { - for (int r = y1; r < y2; ++r) { - const int ch = ri->getSensorType() == ST_BAYER ? ri->FC(r, c) : ri->XTRANSFC(r, c); - - ++pxCount[ch]; - - // Sample the original unprocessed values from RawImage, subtracting black levels. - // Scaling is irrelevant, as we are only interested in the ratio between two spots. - avgs[ch] += ri->data[r][c] - cblacksom[ch]; - } - } - - for (int ch = 0; ch < 3; ++ch) { - avgs[ch] /= pxCount[ch]; - } - - return true; -} - -void calcMedians( - const rtengine::RawImage* ri, - float** data, - int x1, int y1, int x2, int y2, - std::array& meds -) -{ - - MyTime t1, t2, t3; - t1.set(); - - // Channel vectors to calculate medians - std::array, 3> cvs; - - // Sample one every 5 pixels, and push the value in the appropriate channel vector. - // Choose an odd step, not a multiple of the CFA size, to get a chance to visit each channel. - if (ri->getSensorType() == ST_BAYER) { - for (int row = y1; row < y2; row += 5) { - const int c0 = ri->FC(row, x1 + 0); - const int c1 = ri->FC(row, x1 + 5); - int col = x1; - - for (; col < x2 - 5; col += 10) { - cvs[c0].push_back(data[row][col]); - cvs[c1].push_back(data[row][col + 5]); - } - - if (col < x2) { - cvs[c0].push_back(data[row][col]); + avg.r += r; + avg.g += g; + avg.b += b; + max.r = MAX(max.r, r); + max.g = MAX(max.g, g); + max.b = MAX(max.b, b); } } - } else if (ri->getSensorType() == ST_FUJI_XTRANS) { - for (int row = y1; row < y2; row += 5) { - const std::array cs = { - ri->XTRANSFC(row, x1 + 0), - ri->XTRANSFC(row, x1 + 5), - ri->XTRANSFC(row, x1 + 10), - ri->XTRANSFC(row, x1 + 15), - ri->XTRANSFC(row, x1 + 20), - ri->XTRANSFC(row, x1 + 25) - }; - int col = x1; - for (; col < x2 - 25; col += 30) { - for (int c = 0; c < 6; ++c) { - cvs[cs[c]].push_back(data[row][col + c * 5]); - } - } - - for (int c = 0; col < x2; col += 5, ++c) { - cvs[cs[c]].push_back(data[row][col]); - } - } - } - - t2.set(); - - if (settings->verbose) { - printf("Median vector fill loop time us: %d\n", t2.etime(t1)); - } - - t2.set(); - - for (int c = 0; c < 3; ++c) { - // Find median values for each channel - if (!cvs[c].empty()) { - rtengine::findMinMaxPercentile(cvs[c].data(), cvs[c].size(), 0.5f, meds[c], 0.5f, meds[c], true); - } - } - - t3.set(); - - if (settings->verbose) { - printf("Sample count: R=%zu, G=%zu, B=%zu\n", cvs[0].size(), cvs[1].size(), cvs[2].size()); - printf("Median calc time us: %d\n", t3.etime(t2)); - } - -} - -std::array calcWBMults( - const rtengine::ColorTemp& wb, - const rtengine::ImageMatrices& imatrices, - const rtengine::RawImage *ri, - const float ref_pre_mul[4]) -{ - std::array wb_mul; - double r, g, b; - wb.getMultipliers(r, g, b); - wb_mul[0] = imatrices.cam_rgb[0][0] * r + imatrices.cam_rgb[0][1] * g + imatrices.cam_rgb[0][2] * b; - wb_mul[1] = imatrices.cam_rgb[1][0] * r + imatrices.cam_rgb[1][1] * g + imatrices.cam_rgb[1][2] * b; - wb_mul[2] = imatrices.cam_rgb[2][0] * r + imatrices.cam_rgb[2][1] * g + imatrices.cam_rgb[2][2] * b; - - for (int c = 0; c < 3; ++c) { - wb_mul[c] = ri->get_pre_mul(c) / wb_mul[c] / ref_pre_mul[c]; - } - - // Normalize max channel gain to 1.0 - float mg = rtengine::max(wb_mul[0], wb_mul[1], wb_mul[2]); - - for (int c = 0; c < 3; ++c) { - wb_mul[c] /= mg; - } - - return wb_mul; -} - -} - -bool rtengine::RawImageSource::getFilmNegativeExponents(Coord2D spotA, Coord2D spotB, int tran, const procparams::FilmNegativeParams ¤tParams, std::array& newExps) -{ - newExps = { - static_cast(currentParams.redRatio * currentParams.greenExp), - static_cast(currentParams.greenExp), - static_cast(currentParams.blueRatio * currentParams.greenExp) + avg.r /= (spotSize * spotSize); + avg.g /= (spotSize * spotSize); + avg.b /= (spotSize * spotSize); }; - constexpr int spotSize = 32; // TODO: Make this configurable? + if (params->filmNegative.colorSpace == rtengine::FilmNegativeParams::ColorSpace::INPUT) { + avgMax(avg, max); + } else { + // Convert spot image to current working space + imgsrc->convertColorSpace(&spotImg, params->icm, currWB); - Coord spot; - std::array clearVals; - std::array denseVals; + avgMax(avg, max); - // Get channel averages in the two spots, sampling from the original ri->data buffer. - // NOTE: rawData values might be affected by CA correction, FlatField, etc, so: - // rawData[y][x] == (ri->data[y][x] - cblacksom[c]) * scale_mul[c] - // is not always true. To calculate exponents on the exact values, we should keep - // a copy of the rawData buffer after preprocessing. Worth the memory waste? - - // Sample first spot - transformPosition(spotA.x, spotA.y, tran, spot.x, spot.y); - - if (!channelsAvg(ri, W, H, cblacksom, spot, spotSize, clearVals)) { - return false; + // TODO handle custom color profile ! } - // Sample second spot - transformPosition(spotB.x, spotB.y, tran, spot.x, spot.y); - - if (!channelsAvg(ri, W, H, cblacksom, spot, spotSize, denseVals)) { - return false; - } - - // Detect which one is the dense spot, based on green channel - if (clearVals[1] < denseVals[1]) { - std::swap(clearVals, denseVals); - } + // Clip away zeroes or negative numbers, you never know + avg.r = MAX(avg.r, 1.f); + avg.g = MAX(avg.g, 1.f); + avg.b = MAX(avg.b, 1.f); if (settings->verbose) { - printf("Clear film values: R=%g G=%g B=%g\n", static_cast(clearVals[0]), static_cast(clearVals[1]), static_cast(clearVals[2])); - printf("Dense film values: R=%g G=%g B=%g\n", static_cast(denseVals[0]), static_cast(denseVals[1]), static_cast(denseVals[2])); + printf("Average Spot RGB: %f,%f,%f\n", avg.r, avg.g, avg.b); } +} - const float denseGreenRatio = clearVals[1] / denseVals[1]; - // Calculate logarithms in arbitrary base - const auto logBase = - [](float base, float num) -> float - { - return std::log(num) / std::log(base); - }; +void calcMedians( + const rtengine::Imagefloat* input, + int x1, int y1, int x2, int y2, + float &rmed, float &gmed, float &bmed +) +{ + using rtengine::findMinMaxPercentile; - // Calculate exponents for each channel, based on the ratio between the bright and dark values, - // compared to the ratio in the reference channel (green) - for (int ch = 0; ch < 3; ++ch) { - if (ch == 1) { - newExps[ch] = 1.f; // Green is the reference channel - } else { - newExps[ch] = rtengine::LIM(logBase(clearVals[ch] / denseVals[ch], denseGreenRatio), 0.3f, 4.f); + // Channel vectors to calculate medians + std::vector rv, gv, bv; + + const int sz = (x2 - x1) * (y2 - y1); + rv.reserve(sz); + gv.reserve(sz); + bv.reserve(sz); + + + for (int ii = y1; ii < y2; ii ++) { + for (int jj = x1; jj < x2; jj ++) { + rv.push_back(input->r(ii, jj)); + gv.push_back(input->g(ii, jj)); + bv.push_back(input->b(ii, jj)); } } - if (settings->verbose) { - printf("New exponents: R=%g G=%g B=%g\n", static_cast(newExps[0]), static_cast(newExps[1]), static_cast(newExps[2])); - } - - return true; + // Calculate channel medians of the specified area + findMinMaxPercentile(rv.data(), rv.size(), 0.5f, rmed, 0.5f, rmed, true); + findMinMaxPercentile(gv.data(), gv.size(), 0.5f, gmed, 0.5f, gmed, true); + findMinMaxPercentile(bv.data(), bv.size(), 0.5f, bmed, 0.5f, bmed, true); } -bool rtengine::RawImageSource::getRawSpotValues(Coord2D spotCoord, int spotSize, int tran, const procparams::FilmNegativeParams ¶ms, std::array& rawValues) + +RGB getMedians(const rtengine::Imagefloat* input, int borderPercent) { - Coord spot; - transformPosition(spotCoord.x, spotCoord.y, tran, spot.x, spot.y); + float rmed, gmed, bmed; + // Cut 20% border from medians calculation. It will probably contain outlier values + // from the film holder, which will bias the median result. + const int bW = input->getWidth() * borderPercent / 100; + const int bH = input->getHeight() * borderPercent / 100; + calcMedians(input, bW, bH, + input->getWidth() - bW, input->getHeight() - bH, + rmed, gmed, bmed); if (settings->verbose) { - printf("Transformed coords: %d,%d\n", spot.x, spot.y); + printf("Channel medians: R=%g, G=%g, B=%g\n", rmed, gmed, bmed); } - if (spotSize < 4) { + return { rmed, gmed, bmed }; +} + +/* +// TODO not needed for now +void convertColorSpace(Imagefloat* input, const TMatrix &src2xyz, const TMatrix &xyz2dest) +{ + +#ifdef _OPENMP + #pragma omp parallel for +#endif + + for (int i = 0; i < input->getHeight(); i++) { + for (int j = 0; j < input->getWidth(); j++) { + + float newr = input->r(i, j); + float newg = input->g(i, j); + float newb = input->b(i, j); + + float x, y, z; + Color::rgbxyz (newr, newg, newb, x, y, z, src2xyz); + Color::xyz2rgb (x, y, z, newr, newg, newb, xyz2dest); + + input->r(i, j) = newr; + input->g(i, j) = newg; + input->b(i, j) = newb; + + } + } +} +*/ + + +/** + * Perform actual film negative inversion process. + * Returns true if the input and output reference values are not set in params; refIn/refOut will be updated with median-based estimates. + * Otherwise, use provided values in params and return false + */ +bool doProcess(Imagefloat *input, Imagefloat *output, + const FilmNegativeParams ¶ms, const ColorManagementParams &icmParams, + RGB &refIn, RGB &refOut) +{ + bool refsUpdated = false; + + float rexp = -(params.greenExp * params.redRatio); + float gexp = -params.greenExp; + float bexp = -(params.greenExp * params.blueRatio); + + // In case we are processing a thumbnail, reference values might not be set in params, + // so make an estimate on the fly, using channel medians + if (refIn.g <= 0.f) { + // Calc medians, 20% border cut + refIn = getMedians(input, 20); + refsUpdated = true; + } else { + refIn = params.refInput; + } + + if (refOut.g <= 0.f) { + // Median will correspond to gray, 1/24th of max in the output + refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f }; + refsUpdated = true; + } else { + refOut = params.refOutput; + } + + // Apply channel exponents to reference input values, and compute suitable multipliers + // in order to reach reference output values. + float rmult = refOut.r / pow_F(rtengine::max(refIn.r, 1.f), rexp); + float gmult = refOut.g / pow_F(rtengine::max(refIn.g, 1.f), gexp); + float bmult = refOut.b / pow_F(rtengine::max(refIn.b, 1.f), bexp); + + +#ifdef __SSE2__ + const vfloat clipv = F2V(MAXVALF); + const vfloat rexpv = F2V(rexp); + const vfloat gexpv = F2V(gexp); + const vfloat bexpv = F2V(bexp); + const vfloat rmultv = F2V(rmult); + const vfloat gmultv = F2V(gmult); + const vfloat bmultv = F2V(bmult); +#endif + + const int rheight = input->getHeight(); + const int rwidth = input->getWidth(); + + for (int i = 0; i < rheight; i++) { + float *rlinein = input->r(i); + float *glinein = input->g(i); + float *blinein = input->b(i); + float *rlineout = output->r(i); + float *glineout = output->g(i); + float *blineout = output->b(i); + int j = 0; +#ifdef __SSE2__ + + for (; j < rwidth - 3; j += 4) { + STVFU(rlineout[j], vminf(rmultv * pow_F(LVFU(rlinein[j]), rexpv), clipv)); + STVFU(glineout[j], vminf(gmultv * pow_F(LVFU(glinein[j]), gexpv), clipv)); + STVFU(blineout[j], vminf(bmultv * pow_F(LVFU(blinein[j]), bexpv), clipv)); + } + +#endif + + for (; j < rwidth; ++j) { + rlineout[j] = CLIP(rmult * pow_F(rlinein[j], rexp)); + glineout[j] = CLIP(gmult * pow_F(glinein[j], gexp)); + blineout[j] = CLIP(bmult * pow_F(blinein[j], bexp)); + } + } + + return refsUpdated; +} + + +} + + + +bool rtengine::ImProcFunctions::filmNegativeProcess( + Imagefloat *input, Imagefloat *output, FilmNegativeParams &fnp, + const RAWParams &rawParams, const ImageSource* imgsrc, const ColorTemp &currWB) +{ + //BENCHFUNMICRO + + if (!fnp.enabled) { return false; } - // Calculate averages of raw unscaled channels - if (!channelsAvg(ri, W, H, cblacksom, spot, spotSize, rawValues)) { - return false; + bool paramsUpdated = false; + + RGB &refIn = fnp.refInput; + RGB &refOut = fnp.refOutput; + + // If we're opening a profile from an older version, apply the proper multiplier + // compensations to make processing backwards compatible. + + if (fnp.backCompat == FilmNegativeParams::BackCompat::V1) { + // Calc medians, no border cut, compensate currWB in+out + refIn = getMedians(input, 0); + refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f }; + + std::array scale_mul = { 1.f, 1.f, 1.f, 1.f }; + float autoGainComp, rm, gm, bm; + imgsrc->getWBMults(currWB, params->raw, scale_mul, autoGainComp, rm, gm, bm); + + refOut.r *= rm; + refOut.g *= gm; + refOut.b *= bm; + + paramsUpdated = true; + + } else if (fnp.backCompat == FilmNegativeParams::BackCompat::V2) { + + std::array scale_mul = { 1.f, 1.f, 1.f, 1.f }; + float autoGainComp, rm, gm, bm; + imgsrc->getWBMults(currWB, params->raw, scale_mul, autoGainComp, rm, gm, bm); + + float rm2, gm2, bm2; + imgsrc->getWBMults(rtengine::ColorTemp(3500., 1., 1., "Custom"), params->raw, scale_mul, autoGainComp, rm2, gm2, bm2); + float mg = rtengine::max(rm2, gm2, bm2); + rm2 /= mg; + gm2 /= mg; + bm2 /= mg; + + if (fnp.refInput.g == 0.f) { + // Calc medians, 20% border cut + refIn = getMedians(input, 20); + refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f }; + } else if (fnp.refInput.g > 0.f) { + // Calc refInput + refOutput from base levels, compensate currWB in, 3500 out + refIn = fnp.refInput; + refIn.r *= rm * scale_mul[0]; + refIn.g *= gm * scale_mul[1]; + refIn.b *= bm * scale_mul[2]; + refOut = { MAXVALF / 512.f, MAXVALF / 512.f, MAXVALF / 512.f }; + } + + refOut.r *= rm * autoGainComp / rm2; + refOut.g *= gm * autoGainComp / gm2; + refOut.b *= bm * autoGainComp / bm2; + + paramsUpdated = true; + } - if (settings->verbose) { - printf("Raw spot values: R=%g, G=%g, B=%g\n", rawValues[0], rawValues[1], rawValues[2]); + if (settings->verbose && fnp.backCompat != FilmNegativeParams::BackCompat::CURRENT) { + printf("Upgraded from V%d - refIn: R=%g G=%g B=%g refOut: R=%g G=%g B=%g\n", + (int)fnp.backCompat, + static_cast(refIn.r), static_cast(refIn.g), static_cast(refIn.b), + static_cast(refOut.r), static_cast(refOut.g), static_cast(refOut.b)); } - return true; + // FilmNeg params are now upgraded to the latest version + fnp.backCompat = FilmNegativeParams::BackCompat::CURRENT; + + // Perform actual inversion. Return true if reference values are computed from medians + paramsUpdated |= doProcess(input, output, fnp, this->params->icm, refIn, refOut); + + return paramsUpdated; + } -void rtengine::RawImageSource::filmNegativeProcess(const procparams::FilmNegativeParams ¶ms, std::array& filmBaseValues) +void rtengine::ImProcFunctions::filmNegativeProcess(rtengine::Imagefloat *input, rtengine::Imagefloat *output, + const procparams::FilmNegativeParams ¶ms) { -// BENCHFUNMICRO + //BENCHFUNMICRO if (!params.enabled) { return; } - // Exponents are expressed as positive in the parameters, so negate them in order - // to get the reciprocals. - const std::array exps = { - static_cast(-params.redRatio * params.greenExp), - static_cast(-params.greenExp), - static_cast(-params.blueRatio * params.greenExp) - }; + RGB refIn = params.refInput, refOut = params.refOutput; - constexpr float MAX_OUT_VALUE = 65000.f; + doProcess(input, output, params, this->params->icm, refIn, refOut); - // Get multipliers for a known, fixed WB setting, that will be the starting point - // for balancing the converted image. - const std::array wb_mul = calcWBMults( - ColorTemp(3500., 1., 1., "Custom"), imatrices, ri, ref_pre_mul); +} - if (rtengine::settings->verbose) { - printf("Fixed WB mults: %g %g %g\n", wb_mul[0], wb_mul[1], wb_mul[2]); - } +bool rtengine::ImProcCoordinator::getFilmNegativeSpot(int x, int y, const int spotSize, RGB &refInput, RGB &refOutput) +{ + MyMutex::MyLock lock(mProcessing); - // Compensation factor to restore the non-autoWB initialGain (see RawImageSource::load) - const float autoGainComp = camInitialGain / initialGain; + const int tr = getCoarseBitMask(params->coarse); - std::array mults; // Channel normalization multipliers + const Coord2D p = translateCoord(ipf, fw, fh, x, y); - // If film base values are set in params, use those - if (filmBaseValues[0] <= 0.f) { - // ...otherwise, the film negative tool might have just been enabled on this image, - // without any previous setting. So, estimate film base values from channel medians + // Get the average channel values from the sampled spot + RGB avg, max; + getSpotAvgMax(imgsrc, currWB, params, p, tr, spotSize, avg, max); - std::array medians; + float rexp = -(params->filmNegative.greenExp * params->filmNegative.redRatio); + float gexp = -params->filmNegative.greenExp; + float bexp = -(params->filmNegative.greenExp * params->filmNegative.blueRatio); - // Special value for backwards compatibility with profiles saved by RT 5.7 - const bool oldChannelScaling = filmBaseValues[0] == -1.f; + float rmult = params->filmNegative.refOutput.r / pow_F(rtengine::max(params->filmNegative.refInput.r, 1.f), rexp); + float gmult = params->filmNegative.refOutput.g / pow_F(rtengine::max(params->filmNegative.refInput.g, 1.f), gexp); + float bmult = params->filmNegative.refOutput.b / pow_F(rtengine::max(params->filmNegative.refInput.b, 1.f), bexp); - // If using the old channel scaling method, get medians from the whole current image, - // reading values from the already-scaled rawData buffer. - if (oldChannelScaling) { - calcMedians(ri, rawData, 0, 0, W, H, medians); - } else { - // Cut 20% border from medians calculation. It will probably contain outlier values - // from the film holder, which will bias the median result. - const int bW = W * 20 / 100; - const int bH = H * 20 / 100; - calcMedians(ri, rawData, bW, bH, W - bW, H - bH, medians); - } + refInput = avg; - // Un-scale rawData medians - for (int c = 0; c < 3; ++c) { - medians[c] /= scale_mul[c]; - } + refOutput.r = rmult * pow_F(avg.r, rexp); + refOutput.g = gmult * pow_F(avg.g, gexp); + refOutput.b = bmult * pow_F(avg.b, bexp); - if (settings->verbose) { - printf("Channel medians: R=%g, G=%g, B=%g\n", medians[0], medians[1], medians[2]); - } - - for (int c = 0; c < 3; ++c) { - - // Estimate film base values, so that in the output data, each channel - // median will correspond to 1/24th of MAX. - filmBaseValues[c] = pow_F(24.f / 512.f, 1.f / exps[c]) * medians[c]; - - if (oldChannelScaling) { - // If using the old channel scaling method, apply WB multipliers here to undo their - // effect later, since fixed wb compensation was not used in previous version. - // Also, undo the effect of the autoGainComp factor (see below). - filmBaseValues[c] /= pow_F((wb_mul[c] / autoGainComp), 1.f / exps[c]);// / autoGainComp; - } - - } - } + return true; +} - // Calculate multipliers based on previously obtained film base input values. - // Apply current scaling coefficients to raw, unscaled base values. - std::array fb = { - filmBaseValues[0] * scale_mul[0], - filmBaseValues[1] * scale_mul[1], - filmBaseValues[2] * scale_mul[2] - }; + + + + +// ---------- >>> legacy mode >>> --------------- + + +// For backwards compatibility with profiles saved by RT 5.7 - 5.8 +void rtengine::Thumbnail::processFilmNegative( + const procparams::ProcParams ¶ms, + const Imagefloat* baseImg, + const int rwidth, const int rheight +) +{ + + // Channel exponents + const float rexp = -params.filmNegative.redRatio * params.filmNegative.greenExp; + const float gexp = -params.filmNegative.greenExp; + const float bexp = -params.filmNegative.blueRatio * params.filmNegative.greenExp; + + const float MAX_OUT_VALUE = 65000.f; + + // Channel medians + float rmed, gmed, bmed; + + // If using the old method, calculate medians on the whole image + calcMedians(baseImg, 0, 0, rwidth, rheight, rmed, gmed, bmed); if (settings->verbose) { - printf("Input film base values: %g %g %g\n", fb[0], fb[1], fb[2]); + printf("FilmNeg legacy V1 :: Thumbnail input channel medians: %g %g %g\n", rmed, gmed, bmed); } - for (int c = 0; c < 3; ++c) { - // Apply channel exponents, to obtain the corresponding base values in the output data - fb[c] = pow_F(rtengine::max(fb[c], 1.f), exps[c]); + // Calculate output medians + rmed = powf(rmed, rexp); + gmed = powf(gmed, gexp); + bmed = powf(bmed, bexp); - // Determine the channel multiplier so that the film base value is 1/512th of max. - mults[c] = (MAX_OUT_VALUE / 512.f) / fb[c]; + // Calculate output multipliers so that the median value is 1/24 of the output range. + float rmult, gmult, bmult; + rmult = (MAX_OUT_VALUE / 24.f) / rmed; + gmult = (MAX_OUT_VALUE / 24.f) / gmed; + bmult = (MAX_OUT_VALUE / 24.f) / bmed; - // Un-apply the fixed WB multipliers, to reverse their effect later in the WB tool. - // This way, the output image will be adapted to this fixed WB setting - mults[c] /= wb_mul[c]; - - // Also compensate for the initialGain difference between the default scaling (forceAutoWB=true) - // and the non-autoWB scaling (see camInitialGain). - // This effectively restores camera scaling multipliers, and gives us stable multipliers - // (not depending on image content). - mults[c] *= autoGainComp; + float rsum = 0.f, gsum = 0.f, bsum = 0.f; + for (int i = 0; i < rheight; i++) { + for (int j = 0; j < rwidth; j++) { + rsum += baseImg->r(i, j); + gsum += baseImg->g(i, j); + bsum += baseImg->b(i, j); + } } + const float ravg = rsum / (rheight * rwidth); + const float gavg = gsum / (rheight * rwidth); + const float bavg = bsum / (rheight * rwidth); + + // Shifting current WB multipliers, based on channel averages. + rmult /= gavg / ravg; + // gmult /= gAvg / gAvg; green chosen as reference channel + bmult /= gavg / bavg; + if (settings->verbose) { - printf("Output film base values: %g %g %g\n", static_cast(fb[0]), static_cast(fb[1]), static_cast(fb[2])); - printf("Computed multipliers: %g %g %g\n", static_cast(mults[0]), static_cast(mults[1]), static_cast(mults[2])); + printf("FilmNeg legacy V1 :: Thumbnail computed multipliers: %g %g %g\n", static_cast(rmult), static_cast(gmult), static_cast(bmult)); } - - constexpr float CLIP_VAL = 65535.f; - - MyTime t1, t2, t3; - - t1.set(); - - if (ri->getSensorType() == ST_BAYER) { #ifdef __SSE2__ - const vfloat onev = F2V(1.f); - const vfloat clipv = F2V(CLIP_VAL); + const vfloat clipv = F2V(MAXVALF); + const vfloat rexpv = F2V(rexp); + const vfloat gexpv = F2V(gexp); + const vfloat bexpv = F2V(bexp); + const vfloat rmultv = F2V(rmult); + const vfloat gmultv = F2V(gmult); + const vfloat bmultv = F2V(bmult); #endif -#ifdef _OPENMP - #pragma omp parallel for schedule(dynamic, 16) -#endif - - for (int row = 0; row < H; ++row) { - int col = 0; - // Avoid trouble with zeroes, minimum pixel value is 1. - const float exps0 = exps[FC(row, col)]; - const float exps1 = exps[FC(row, col + 1)]; - const float mult0 = mults[FC(row, col)]; - const float mult1 = mults[FC(row, col + 1)]; + for (int i = 0; i < rheight; i++) { + float *rline = baseImg->r(i); + float *gline = baseImg->g(i); + float *bline = baseImg->b(i); + int j = 0; #ifdef __SSE2__ - const vfloat expsv = _mm_setr_ps(exps0, exps1, exps0, exps1); - const vfloat multsv = _mm_setr_ps(mult0, mult1, mult0, mult1); - for (; col < W - 3; col += 4) { - STVFU(rawData[row][col], vminf(multsv * pow_F(vmaxf(LVFU(rawData[row][col]), onev), expsv), clipv)); - } - -#endif // __SSE2__ - - for (; col < W - 1; col += 2) { - rawData[row][col] = rtengine::min(mult0 * pow_F(rtengine::max(rawData[row][col], 1.f), exps0), CLIP_VAL); - rawData[row][col + 1] = rtengine::min(mult1 * pow_F(rtengine::max(rawData[row][col + 1], 1.f), exps1), CLIP_VAL); - } - - if (col < W) { - rawData[row][col] = rtengine::min(mult0 * pow_F(rtengine::max(rawData[row][col], 1.f), exps0), CLIP_VAL); - } + for (; j < rwidth - 3; j += 4) { + STVFU(rline[j], vminf(rmultv * pow_F(LVFU(rline[j]), rexpv), clipv)); + STVFU(gline[j], vminf(gmultv * pow_F(LVFU(gline[j]), gexpv), clipv)); + STVFU(bline[j], vminf(bmultv * pow_F(LVFU(bline[j]), bexpv), clipv)); } - } else if (ri->getSensorType() == ST_FUJI_XTRANS) { -#ifdef __SSE2__ - const vfloat onev = F2V(1.f); - const vfloat clipv = F2V(CLIP_VAL); + #endif -#ifdef _OPENMP - #pragma omp parallel for schedule(dynamic, 16) -#endif - - for (int row = 0; row < H; row ++) { - int col = 0; - // Avoid trouble with zeroes, minimum pixel value is 1. - const std::array expsc = { - exps[ri->XTRANSFC(row, 0)], - exps[ri->XTRANSFC(row, 1)], - exps[ri->XTRANSFC(row, 2)], - exps[ri->XTRANSFC(row, 3)], - exps[ri->XTRANSFC(row, 4)], - exps[ri->XTRANSFC(row, 5)] - }; - const std::array multsc = { - mults[ri->XTRANSFC(row, 0)], - mults[ri->XTRANSFC(row, 1)], - mults[ri->XTRANSFC(row, 2)], - mults[ri->XTRANSFC(row, 3)], - mults[ri->XTRANSFC(row, 4)], - mults[ri->XTRANSFC(row, 5)] - }; -#ifdef __SSE2__ - const vfloat expsv0 = _mm_setr_ps(expsc[0], expsc[1], expsc[2], expsc[3]); - const vfloat expsv1 = _mm_setr_ps(expsc[4], expsc[5], expsc[0], expsc[1]); - const vfloat expsv2 = _mm_setr_ps(expsc[2], expsc[3], expsc[4], expsc[5]); - const vfloat multsv0 = _mm_setr_ps(multsc[0], multsc[1], multsc[2], multsc[3]); - const vfloat multsv1 = _mm_setr_ps(multsc[4], multsc[5], multsc[0], multsc[1]); - const vfloat multsv2 = _mm_setr_ps(multsc[2], multsc[3], multsc[4], multsc[5]); - - for (; col < W - 11; col += 12) { - STVFU(rawData[row][col], vminf(multsv0 * pow_F(vmaxf(LVFU(rawData[row][col]), onev), expsv0), clipv)); - STVFU(rawData[row][col + 4], vminf(multsv1 * pow_F(vmaxf(LVFU(rawData[row][col + 4]), onev), expsv1), clipv)); - STVFU(rawData[row][col + 8], vminf(multsv2 * pow_F(vmaxf(LVFU(rawData[row][col + 8]), onev), expsv2), clipv)); - } - -#endif // __SSE2__ - - for (; col < W - 5; col += 6) { - for (int c = 0; c < 6; ++c) { - rawData[row][col + c] = rtengine::min(multsc[c] * pow_F(rtengine::max(rawData[row][col + c], 1.f), expsc[c]), CLIP_VAL); - } - } - - for (int c = 0; col < W; col++, c++) { - rawData[row][col + c] = rtengine::min(multsc[c] * pow_F(rtengine::max(rawData[row][col + c], 1.f), expsc[c]), CLIP_VAL); - } + for (; j < rwidth; ++j) { + rline[j] = CLIP(rmult * pow_F(rline[j], rexp)); + gline[j] = CLIP(gmult * pow_F(gline[j], gexp)); + bline[j] = CLIP(bmult * pow_F(bline[j], bexp)); } } - - t2.set(); - - if (settings->verbose) { - printf("Pow loop time us: %d\n", t2.etime(t1)); - } - - t2.set(); - - PixelsMap bitmapBads(W, H); - - int totBP = 0; // Hold count of bad pixels to correct - - if (ri->getSensorType() == ST_BAYER) { -#ifdef _OPENMP - #pragma omp parallel for reduction(+:totBP) schedule(dynamic,16) -#endif - - for (int i = 0; i < H; ++i) { - for (int j = 0; j < W; ++j) { - if (rawData[i][j] >= MAX_OUT_VALUE) { - bitmapBads.set(j, i); - ++totBP; - } - } - } - - if (totBP > 0) { - interpolateBadPixelsBayer(bitmapBads, rawData); - } - - } else if (ri->getSensorType() == ST_FUJI_XTRANS) { -#ifdef _OPENMP - #pragma omp parallel for reduction(+:totBP) schedule(dynamic,16) -#endif - - for (int i = 0; i < H; ++i) { - for (int j = 0; j < W; ++j) { - if (rawData[i][j] >= MAX_OUT_VALUE) { - bitmapBads.set(j, i); - totBP++; - } - } - } - - if (totBP > 0) { - interpolateBadPixelsXtrans(bitmapBads); - } - } - - t3.set(); - - if (settings->verbose) { - printf("Bad pixels count: %d\n", totBP); - printf("Bad pixels interpolation time us: %d\n", t3.etime(t2)); - } } + + +// For backwards compatibility with intermediate dev version (see filmneg_stable_mults branch) +void rtengine::Thumbnail::processFilmNegativeV2( + const procparams::ProcParams ¶ms, + const Imagefloat* baseImg, + const int rwidth, const int rheight +) +{ + + // Channel exponents + const float rexp = -params.filmNegative.redRatio * params.filmNegative.greenExp; + const float gexp = -params.filmNegative.greenExp; + const float bexp = -params.filmNegative.blueRatio * params.filmNegative.greenExp; + + // Calculate output multipliers + float rmult, gmult, bmult; + + const float MAX_OUT_VALUE = 65000.f; + + // If the film base values are not set in params, estimate multipliers from each channel's median value. + if (params.filmNegative.refInput.r <= 0.f) { + + // Channel medians + float rmed, gmed, bmed; + + // The new method cuts out a 20% border from medians calculation. + const int bW = rwidth * 20 / 100; + const int bH = rheight * 20 / 100; + calcMedians(baseImg, bW, bH, rwidth - bW, rheight - bH, rmed, gmed, bmed); + + if (settings->verbose) { + printf("FilmNeg legacy V2 :: Thumbnail input channel medians: %g %g %g\n", rmed, gmed, bmed); + } + + // Calculate output medians + rmed = powf(rmed, rexp); + gmed = powf(gmed, gexp); + bmed = powf(bmed, bexp); + + // Calculate output multipliers so that the median value is 1/24 of the output range. + rmult = (MAX_OUT_VALUE / 24.f) / rmed; + gmult = (MAX_OUT_VALUE / 24.f) / gmed; + bmult = (MAX_OUT_VALUE / 24.f) / bmed; + + } else { + + // Read film-base values from params + float rbase = params.filmNegative.refInput.r; // redBase; + float gbase = params.filmNegative.refInput.g; // greenBase; + float bbase = params.filmNegative.refInput.b; // blueBase; + + // Reconstruct scale_mul coefficients from thumbnail metadata: + // redMultiplier / camwbRed is pre_mul[0] + // pre_mul[0] * scaleGain is scale_mul[0] + // Apply channel scaling to raw (unscaled) input base values, to + // match with actual (scaled) data in baseImg + rbase *= (redMultiplier / camwbRed) * scaleGain; + gbase *= (greenMultiplier / camwbGreen) * scaleGain; + bbase *= (blueMultiplier / camwbBlue) * scaleGain; + + if (settings->verbose) { + printf("FilmNeg legacy V2 :: Thumbnail input film base values: %g %g %g\n", rbase, gbase, bbase); + } + + // Apply exponents to get output film base values + rbase = powf(rbase, rexp); + gbase = powf(gbase, gexp); + bbase = powf(bbase, bexp); + + // Calculate multipliers so that film base value is 1/512th of the output range. + rmult = (MAX_OUT_VALUE / 512.f) / rbase; + gmult = (MAX_OUT_VALUE / 512.f) / gbase; + bmult = (MAX_OUT_VALUE / 512.f) / bbase; + + } + + + // Get and un-apply multipliers to adapt the thumbnail to a known fixed WB setting, + // as in the main image processing. + + double r, g, b; + ColorTemp(3500., 1., 1., "Custom").getMultipliers(r, g, b); + //iColorMatrix is cam_rgb + const double rm = camwbRed / (iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b); + const double gm = camwbGreen / (iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b); + const double bm = camwbBlue / (iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b); + + // Normalize max WB multiplier to 1.f + const double m = max(rm, gm, bm); + rmult /= rm / m; + gmult /= gm / m; + bmult /= bm / m; + + + if (settings->verbose) { + printf("FilmNeg legacy V2 :: Thumbnail computed multipliers: %g %g %g\n", static_cast(rmult), static_cast(gmult), static_cast(bmult)); + } + + +#ifdef __SSE2__ + const vfloat clipv = F2V(MAXVALF); + const vfloat rexpv = F2V(rexp); + const vfloat gexpv = F2V(gexp); + const vfloat bexpv = F2V(bexp); + const vfloat rmultv = F2V(rmult); + const vfloat gmultv = F2V(gmult); + const vfloat bmultv = F2V(bmult); +#endif + + for (int i = 0; i < rheight; i++) { + float *rline = baseImg->r(i); + float *gline = baseImg->g(i); + float *bline = baseImg->b(i); + int j = 0; +#ifdef __SSE2__ + + for (; j < rwidth - 3; j += 4) { + STVFU(rline[j], vminf(rmultv * pow_F(LVFU(rline[j]), rexpv), clipv)); + STVFU(gline[j], vminf(gmultv * pow_F(LVFU(gline[j]), gexpv), clipv)); + STVFU(bline[j], vminf(bmultv * pow_F(LVFU(bline[j]), bexpv), clipv)); + } + +#endif + + for (; j < rwidth; ++j) { + rline[j] = CLIP(rmult * pow_F(rline[j], rexp)); + gline[j] = CLIP(gmult * pow_F(gline[j], gexp)); + bline[j] = CLIP(bmult * pow_F(bline[j], bexp)); + } + } +} + +// ----------------- <<< legacy mode <<< ------------ + + diff --git a/rtengine/filmnegativethumb.cc b/rtengine/filmnegativethumb.cc deleted file mode 100644 index 57f2601f9..000000000 --- a/rtengine/filmnegativethumb.cc +++ /dev/null @@ -1,237 +0,0 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Alberto Romei - * - * 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 - -#include "colortemp.h" -#include "LUT.h" -#include "rtengine.h" -#include "rtthumbnail.h" -#include "opthelper.h" -#include "sleef.h" -#include "rt_algo.h" -#include "settings.h" -#include "procparams.h" -#define BENCHMARK -#include "StopWatch.h" - -namespace -{ - -using rtengine::Imagefloat; -using rtengine::findMinMaxPercentile; - - -void calcMedians( - const Imagefloat* baseImg, - const int x1, const int y1, - const int x2, const int y2, - float &rmed, float &gmed, float &bmed -) -{ - // Channel vectors to calculate medians - std::vector rv, gv, bv; - - const int sz = std::max(0, (y2 - y1) * (x2 - x1)); - rv.reserve(sz); - gv.reserve(sz); - bv.reserve(sz); - - for (int i = y1; i < y2; i++) { - for (int j = x1; j < x2; j++) { - rv.push_back(baseImg->r(i, j)); - gv.push_back(baseImg->g(i, j)); - bv.push_back(baseImg->b(i, j)); - } - } - - // Calculate channel medians from whole image - findMinMaxPercentile(rv.data(), rv.size(), 0.5f, rmed, 0.5f, rmed, true); - findMinMaxPercentile(gv.data(), gv.size(), 0.5f, gmed, 0.5f, gmed, true); - findMinMaxPercentile(bv.data(), bv.size(), 0.5f, bmed, 0.5f, bmed, true); -} - -} - -void rtengine::Thumbnail::processFilmNegative( - const procparams::ProcParams ¶ms, - const Imagefloat* baseImg, - const int rwidth, const int rheight -) -{ - - // Channel exponents - const float rexp = -params.filmNegative.redRatio * params.filmNegative.greenExp; - const float gexp = -params.filmNegative.greenExp; - const float bexp = -params.filmNegative.blueRatio * params.filmNegative.greenExp; - - // Calculate output multipliers - float rmult, gmult, bmult; - - const float MAX_OUT_VALUE = 65000.f; - - // For backwards compatibility with profiles saved by RT 5.7 - const bool oldChannelScaling = params.filmNegative.redBase == -1.f; - - // If the film base values are not set in params, estimate multipliers from each channel's median value. - if (params.filmNegative.redBase <= 0.f) { - - // Channel medians - float rmed, gmed, bmed; - - if (oldChannelScaling) { - // If using the old method, calculate nedians on the whole image - calcMedians(baseImg, 0, 0, rwidth, rheight, rmed, gmed, bmed); - } else { - // The new method cuts out a 20% border from medians calculation. - const int bW = rwidth * 20 / 100; - const int bH = rheight * 20 / 100; - calcMedians(baseImg, bW, bH, rwidth - bW, rheight - bH, rmed, gmed, bmed); - } - - if (settings->verbose) { - printf("Thumbnail input channel medians: %g %g %g\n", rmed, gmed, bmed); - } - - // Calculate output medians - rmed = powf(rmed, rexp); - gmed = powf(gmed, gexp); - bmed = powf(bmed, bexp); - - // Calculate output multipliers so that the median value is 1/24 of the output range. - rmult = (MAX_OUT_VALUE / 24.f) / rmed; - gmult = (MAX_OUT_VALUE / 24.f) / gmed; - bmult = (MAX_OUT_VALUE / 24.f) / bmed; - - } else { - - // Read film-base values from params - float rbase = params.filmNegative.redBase; - float gbase = params.filmNegative.greenBase; - float bbase = params.filmNegative.blueBase; - - // Reconstruct scale_mul coefficients from thumbnail metadata: - // redMultiplier / camwbRed is pre_mul[0] - // pre_mul[0] * scaleGain is scale_mul[0] - // Apply channel scaling to raw (unscaled) input base values, to - // match with actual (scaled) data in baseImg - rbase *= (redMultiplier / camwbRed) * scaleGain; - gbase *= (greenMultiplier / camwbGreen) * scaleGain; - bbase *= (blueMultiplier / camwbBlue) * scaleGain; - - if (settings->verbose) { - printf("Thumbnail input film base values: %g %g %g\n", rbase, gbase, bbase); - } - - // Apply exponents to get output film base values - rbase = powf(rbase, rexp); - gbase = powf(gbase, gexp); - bbase = powf(bbase, bexp); - - if (settings->verbose) { - printf("Thumbnail output film base values: %g %g %g\n", rbase, gbase, bbase); - } - - // Calculate multipliers so that film base value is 1/512th of the output range. - rmult = (MAX_OUT_VALUE / 512.f) / rbase; - gmult = (MAX_OUT_VALUE / 512.f) / gbase; - bmult = (MAX_OUT_VALUE / 512.f) / bbase; - - } - - - if (oldChannelScaling) { - // Need to calculate channel averages, to fake the same conditions - // found in rawimagesource, where get_ColorsCoeff is called with - // forceAutoWB=true. - float rsum = 0.f, gsum = 0.f, bsum = 0.f; - - for (int i = 0; i < rheight; i++) { - for (int j = 0; j < rwidth; j++) { - rsum += baseImg->r(i, j); - gsum += baseImg->g(i, j); - bsum += baseImg->b(i, j); - } - } - - const float ravg = rsum / (rheight * rwidth); - const float gavg = gsum / (rheight * rwidth); - const float bavg = bsum / (rheight * rwidth); - - // Shifting current WB multipliers, based on channel averages. - rmult /= gavg / ravg; - // gmult /= gAvg / gAvg; green chosen as reference channel - bmult /= gavg / bavg; - - } else { - - // Get and un-apply multipliers to adapt the thumbnail to a known fixed WB setting, - // as in the main image processing. - - double r, g, b; - ColorTemp(3500., 1., 1., "Custom").getMultipliers(r, g, b); - //iColorMatrix is cam_rgb - const double rm = camwbRed / (iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b); - const double gm = camwbGreen / (iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b); - const double bm = camwbBlue / (iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b); - - // Normalize max WB multiplier to 1.f - const double m = max(rm, gm, bm); - rmult /= rm / m; - gmult /= gm / m; - bmult /= bm / m; - } - - - if (settings->verbose) { - printf("Thumbnail computed multipliers: %g %g %g\n", static_cast(rmult), static_cast(gmult), static_cast(bmult)); - } - - -#ifdef __SSE2__ - const vfloat clipv = F2V(MAXVALF); - const vfloat rexpv = F2V(rexp); - const vfloat gexpv = F2V(gexp); - const vfloat bexpv = F2V(bexp); - const vfloat rmultv = F2V(rmult); - const vfloat gmultv = F2V(gmult); - const vfloat bmultv = F2V(bmult); -#endif - - for (int i = 0; i < rheight; i++) { - float *rline = baseImg->r(i); - float *gline = baseImg->g(i); - float *bline = baseImg->b(i); - int j = 0; -#ifdef __SSE2__ - - for (; j < rwidth - 3; j += 4) { - STVFU(rline[j], vminf(rmultv * pow_F(LVFU(rline[j]), rexpv), clipv)); - STVFU(gline[j], vminf(gmultv * pow_F(LVFU(gline[j]), gexpv), clipv)); - STVFU(bline[j], vminf(bmultv * pow_F(LVFU(bline[j]), bexpv), clipv)); - } - -#endif - - for (; j < rwidth; ++j) { - rline[j] = CLIP(rmult * pow_F(rline[j], rexp)); - gline[j] = CLIP(gmult * pow_F(gline[j], gexp)); - bline[j] = CLIP(bmult * pow_F(bline[j], bexp)); - } - } -} diff --git a/rtengine/imagesource.h b/rtengine/imagesource.h index ea049d37c..a1c3731f1 100644 --- a/rtengine/imagesource.h +++ b/rtengine/imagesource.h @@ -92,9 +92,6 @@ public: ~ImageSource () override {} virtual int load (const Glib::ustring &fname) = 0; virtual void preprocess (const procparams::RAWParams &raw, const procparams::LensProfParams &lensProf, const procparams::CoarseTransformParams& coarse, bool prepareDenoise = true) {}; - virtual void filmNegativeProcess (const procparams::FilmNegativeParams ¶ms, std::array& filmBaseValues) {}; - virtual bool getFilmNegativeExponents (Coord2D spotA, Coord2D spotB, int tran, const procparams::FilmNegativeParams& currentParams, std::array& newExps) { return false; }; - virtual bool getRawSpotValues (Coord2D spot, int spotSize, int tran, const procparams::FilmNegativeParams ¶ms, std::array& rawValues) { return false; }; virtual void demosaic (const procparams::RAWParams &raw, bool autoContrast, double &contrastThreshold, bool cache = false) {}; virtual void retinex (const procparams::ColorManagementParams& cmp, const procparams::RetinexParams &deh, const procparams::ToneCurveParams& Tc, LUTf & cdcurve, LUTf & mapcurve, const RetinextransmissionCurve & dehatransmissionCurve, const RetinexgaintransmissionCurve & dehagaintransmissionCurve, multi_array2D &conversionBuffer, bool dehacontlutili, bool mapcontlutili, bool useHsl, float &minCD, float &maxCD, float &mini, float &maxi, float &Tmean, float &Tsigma, float &Tmin, float &Tmax, LUTu &histLRETI) {}; virtual void retinexPrepareCurves (const procparams::RetinexParams &retinexParams, LUTf &cdcurve, LUTf &mapcurve, RetinextransmissionCurve &retinextransmissionCurve, RetinexgaintransmissionCurve &retinexgaintransmissionCurve, bool &retinexcontlutili, bool &mapcontlutili, bool &useHsl, LUTu & lhist16RETI, LUTu & histLRETI) {}; @@ -110,6 +107,7 @@ public: virtual int getFrameCount () = 0; virtual int getFlatFieldAutoClipValue () = 0; + virtual void getWBMults (const ColorTemp &ctemp, const procparams::RAWParams &raw, std::array& scale_mul, float &autoGainComp, float &rm, float &gm, float &bm) const = 0; // use right after demosaicing image, add coarse transformation and put the result in the provided Imagefloat* virtual void getImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp, const procparams::ToneCurveParams &hlp, const procparams::RAWParams &raw) = 0; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 09a226ded..e63bb19f4 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -48,19 +48,6 @@ namespace constexpr int VECTORSCOPE_SIZE = 128; -using rtengine::Coord2D; -Coord2D translateCoord(const rtengine::ImProcFunctions& ipf, int fw, int fh, int x, int y) { - - const std::vector points = {Coord2D(x, y)}; - - std::vector red; - std::vector green; - std::vector blue; - ipf.transCoord(fw, fh, points, red, green, blue); - - return green[0]; -} - } namespace rtengine @@ -369,26 +356,6 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) hist_raw_dirty = !(hListener && hListener->updateHistogramRaw()); highDetailPreprocessComputed = highDetailNeeded; - - // After preprocess, run film negative processing if enabled - if ( - (todo & M_RAW) - && ( - imgsrc->getSensorType() == ST_BAYER - || imgsrc->getSensorType() == ST_FUJI_XTRANS - ) - && params->filmNegative.enabled - ) { - std::array filmBaseValues = { - static_cast(params->filmNegative.redBase), - static_cast(params->filmNegative.greenBase), - static_cast(params->filmNegative.blueBase) - }; - imgsrc->filmNegativeProcess(params->filmNegative, filmBaseValues); - if (filmNegListener && params->filmNegative.redBase <= 0.f) { - filmNegListener->filmBaseValuesChanged(filmBaseValues); - } - } } /* @@ -660,7 +627,27 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } } */ - imgsrc->convertColorSpace(orig_prev, params->icm, currWB); + + if (params->filmNegative.enabled) { + + // Process film negative AFTER colorspace conversion + if (params->filmNegative.colorSpace != FilmNegativeParams::ColorSpace::INPUT) { + imgsrc->convertColorSpace(orig_prev, params->icm, currWB); + } + + // Perform negative inversion. If needed, upgrade filmNegative params for backwards compatibility with old profiles + if (ipf.filmNegativeProcess(orig_prev, orig_prev, params->filmNegative, params->raw, imgsrc, currWB) && filmNegListener) { + filmNegListener->filmRefValuesChanged(params->filmNegative.refInput, params->filmNegative.refOutput); + } + + // Process film negative BEFORE colorspace conversion (legacy mode) + if (params->filmNegative.colorSpace == FilmNegativeParams::ColorSpace::INPUT) { + imgsrc->convertColorSpace(orig_prev, params->icm, currWB); + } + + } else { + imgsrc->convertColorSpace(orig_prev, params->icm, currWB); + } ipf.firstAnalysis(orig_prev, *params, vhist16); } @@ -2116,25 +2103,7 @@ void ImProcCoordinator::getSpotWB(int x, int y, int rect, double& temp, double& } } -bool ImProcCoordinator::getFilmNegativeExponents(int xA, int yA, int xB, int yB, std::array& newExps) -{ - MyMutex::MyLock lock(mProcessing); - const int tr = getCoarseBitMask(params->coarse); - - const Coord2D p1 = translateCoord(ipf, fw, fh, xA, yA); - const Coord2D p2 = translateCoord(ipf, fw, fh, xB, yB); - - return imgsrc->getFilmNegativeExponents(p1, p2, tr, params->filmNegative, newExps); -} - -bool ImProcCoordinator::getRawSpotValues(int x, int y, int spotSize, std::array& rawValues) -{ - MyMutex::MyLock lock(mProcessing); - - return imgsrc->getRawSpotValues(translateCoord(ipf, fw, fh, x, y), spotSize, - getCoarseBitMask(params->coarse), params->filmNegative, rawValues); -} void ImProcCoordinator::getAutoCrop(double ratio, int &x, int &y, int &w, int &h) { @@ -2405,6 +2374,7 @@ void ImProcCoordinator::process() || params->dirpyrequalizer != nextParams->dirpyrequalizer || params->dehaze != nextParams->dehaze || params->pdsharpening != nextParams->pdsharpening + || params->filmNegative != nextParams->filmNegative || sharpMaskChanged; sharpMaskChanged = false; diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index 3d84a0aad..8097dd9ce 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -407,8 +407,7 @@ public: bool getAutoWB (double& temp, double& green, double equal, double tempBias) override; void getCamWB (double& temp, double& green) override; void getSpotWB (int x, int y, int rectSize, double& temp, double& green) override; - bool getFilmNegativeExponents(int xA, int yA, int xB, int yB, std::array& newExps) override; - bool getRawSpotValues(int x, int y, int spotSize, std::array& rawValues) override; + bool getFilmNegativeSpot(int x, int y, int spotSize, FilmNegativeParams::RGB &refInput, FilmNegativeParams::RGB &refOutput) override; void getAutoCrop (double ratio, int &x, int &y, int &w, int &h) override; bool getHighQualComputed() override; void setHighQualComputed() override; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index aa24aac6a..e7b6b176d 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -88,6 +88,7 @@ struct DehazeParams; struct FattalToneMappingParams; struct ColorManagementParams; struct DirPyrDenoiseParams; +struct FilmNegativeParams; struct LocalContrastParams; struct LocallabParams; struct SharpeningParams; @@ -149,6 +150,12 @@ public: bool needsTransform(int oW, int oH, int rawRotationDeg, const FramesMetaData *metadata) const; bool needsPCVignetting() const; + + bool filmNegativeProcess(rtengine::Imagefloat *input, rtengine::Imagefloat *output, procparams::FilmNegativeParams &fnp, + const procparams::RAWParams &rawParams, const rtengine::ImageSource* imgsrc, const rtengine::ColorTemp &currWB); + + void filmNegativeProcess(rtengine::Imagefloat *input, rtengine::Imagefloat *output, const procparams::FilmNegativeParams ¶ms); + float calcGradientFactor (const struct grad_params& gp, int x, int y); void firstAnalysis(const Imagefloat* const working, const procparams::ProcParams ¶ms, LUTu & vhist16); void updateColorProfiles(const Glib::ustring& monitorProfile, RenderingIntent monitorIntent, bool softProof, bool gamutCheck); diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index fef32b05d..e6f06480d 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -112,6 +112,16 @@ void getFromKeyfile( value = keyfile.get_double(group_name, key); } +void getFromKeyfile( + const Glib::KeyFile& keyfile, + const Glib::ustring& group_name, + const Glib::ustring& key, + float& value +) +{ + value = static_cast(keyfile.get_double(group_name, key)); +} + void getFromKeyfile( const Glib::KeyFile& keyfile, const Glib::ustring& group_name, @@ -153,6 +163,21 @@ void getFromKeyfile( rtengine::sanitizeCurve(value); } +void getFromKeyfile( + const Glib::KeyFile& keyfile, + const Glib::ustring& group_name, + const Glib::ustring& key, + rtengine::procparams::FilmNegativeParams::RGB& value +) +{ + const std::vector v = keyfile.get_double_list(group_name, key); + if(v.size() >= 3) { + value.r = v[0]; + value.g = v[1]; + value.b = v[2]; + } +} + template bool assignFromKeyfile( const Glib::KeyFile& keyfile, @@ -219,6 +244,16 @@ void putToKeyfile( keyfile.set_integer(group_name, key, value); } +void putToKeyfile( + const Glib::ustring& group_name, + const Glib::ustring& key, + float value, + Glib::KeyFile& keyfile +) +{ + keyfile.set_double(group_name, key, static_cast(value)); +} + void putToKeyfile( const Glib::ustring& group_name, const Glib::ustring& key, @@ -271,6 +306,18 @@ void putToKeyfile( keyfile.set_double_list(group_name, key, list); } +void putToKeyfile( + const Glib::ustring& group_name, + const Glib::ustring& key, + const rtengine::procparams::FilmNegativeParams::RGB& value, + Glib::KeyFile& keyfile +) +{ + const std::vector vec = { value.r, value.g, value.b }; + keyfile.set_double_list(group_name, key, vec); +} + + template bool saveToKeyfile( bool save, @@ -5013,12 +5060,35 @@ FilmNegativeParams::FilmNegativeParams() : redRatio(1.36), greenExp(1.5), blueRatio(0.86), - redBase(0), - greenBase(0), - blueBase(0) + refInput({0.0, 0.0, 0.0}), + refOutput({0.0, 0.0, 0.0}), + colorSpace(ColorSpace::WORKING), + backCompat(BackCompat::CURRENT) { } +bool FilmNegativeParams::RGB::operator ==(const FilmNegativeParams::RGB& other) const +{ + return + r == other.r + && g == other.g + && b == other.b; +} + +bool FilmNegativeParams::RGB::operator !=(const FilmNegativeParams::RGB& other) const +{ + return !(*this == other); +} + +FilmNegativeParams::RGB FilmNegativeParams::RGB::operator *(const FilmNegativeParams::RGB& other) const +{ + return { + (*this).r * other.r, + (*this).g * other.g, + (*this).b * other.b + }; +} + bool FilmNegativeParams::operator ==(const FilmNegativeParams& other) const { return @@ -5026,9 +5096,10 @@ bool FilmNegativeParams::operator ==(const FilmNegativeParams& other) const && redRatio == other.redRatio && greenExp == other.greenExp && blueRatio == other.blueRatio - && redBase == other.redBase - && greenBase == other.greenBase - && blueBase == other.blueBase; + && refInput == other.refInput + && refOutput == other.refOutput + && colorSpace == other.colorSpace + && backCompat == other.backCompat; } bool FilmNegativeParams::operator !=(const FilmNegativeParams& other) const @@ -6506,9 +6577,18 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->filmNegative.redRatio, "Film Negative", "RedRatio", filmNegative.redRatio, keyFile); saveToKeyfile(!pedited || pedited->filmNegative.greenExp, "Film Negative", "GreenExponent", filmNegative.greenExp, keyFile); saveToKeyfile(!pedited || pedited->filmNegative.blueRatio, "Film Negative", "BlueRatio", filmNegative.blueRatio, keyFile); - saveToKeyfile(!pedited || pedited->filmNegative.baseValues, "Film Negative", "RedBase", filmNegative.redBase, keyFile); - saveToKeyfile(!pedited || pedited->filmNegative.baseValues, "Film Negative", "GreenBase", filmNegative.greenBase, keyFile); - saveToKeyfile(!pedited || pedited->filmNegative.baseValues, "Film Negative", "BlueBase", filmNegative.blueBase, keyFile); + + // FIXME to be removed: only for backwards compatibility with an intermediate dev version + if (filmNegative.backCompat == FilmNegativeParams::BackCompat::V2) { + saveToKeyfile(!pedited || pedited->filmNegative.refInput, "Film Negative", "RedBase", filmNegative.refInput.r, keyFile); + saveToKeyfile(!pedited || pedited->filmNegative.refInput, "Film Negative", "GreenBase", filmNegative.refInput.g, keyFile); + saveToKeyfile(!pedited || pedited->filmNegative.refInput, "Film Negative", "BlueBase", filmNegative.refInput.b, keyFile); + } + + saveToKeyfile(!pedited || pedited->filmNegative.colorSpace, "Film Negative", "ColorSpace", toUnderlying(filmNegative.colorSpace), keyFile); + saveToKeyfile(!pedited || pedited->filmNegative.refInput, "Film Negative", "RefInput", filmNegative.refInput, keyFile); + saveToKeyfile(!pedited || pedited->filmNegative.refOutput, "Film Negative", "RefOutput", filmNegative.refOutput, keyFile); + saveToKeyfile(true, "Film Negative", "BackCompat", toUnderlying(filmNegative.backCompat), keyFile); // Preprocess WB saveToKeyfile(!pedited || pedited->raw.preprocessWB.mode, "RAW Preprocess WB", "Mode", toUnderlying(raw.preprocessWB.mode), keyFile); @@ -8868,23 +8948,48 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Film Negative", "RedRatio", pedited, filmNegative.redRatio, pedited->filmNegative.redRatio); assignFromKeyfile(keyFile, "Film Negative", "GreenExponent", pedited, filmNegative.greenExp, pedited->filmNegative.greenExp); assignFromKeyfile(keyFile, "Film Negative", "BlueRatio", pedited, filmNegative.blueRatio, pedited->filmNegative.blueRatio); - if (ppVersion >= 347) { + + if (ppVersion < 347) { + // Backwards compatibility with RT v5.8 + filmNegative.colorSpace = FilmNegativeParams::ColorSpace::INPUT; + filmNegative.backCompat = FilmNegativeParams::BackCompat::V1; + if (pedited) { + pedited->filmNegative.refInput = true; + pedited->filmNegative.refOutput = true; + pedited->filmNegative.colorSpace = true; + } + + } else if (!keyFile.has_key("Film Negative", "RefInput")) { + // Backwards compatibility with intermediate dev version (after v5.8) using film base values bool r, g, b; - assignFromKeyfile(keyFile, "Film Negative", "RedBase", pedited, filmNegative.redBase, r); - assignFromKeyfile(keyFile, "Film Negative", "GreenBase", pedited, filmNegative.greenBase, g); - assignFromKeyfile(keyFile, "Film Negative", "BlueBase", pedited, filmNegative.blueBase, b); + assignFromKeyfile(keyFile, "Film Negative", "RedBase", pedited, filmNegative.refInput.r, r); + assignFromKeyfile(keyFile, "Film Negative", "GreenBase", pedited, filmNegative.refInput.g, g); + assignFromKeyfile(keyFile, "Film Negative", "BlueBase", pedited, filmNegative.refInput.b, b); if (pedited) { - pedited->filmNegative.baseValues = r || g || b; + pedited->filmNegative.refInput = r || g || b; + pedited->filmNegative.refOutput = r || g || b; + pedited->filmNegative.colorSpace = true; } - } else { - // Backwards compatibility with film negative in RT 5.7: use special film base value -1, - // to signal that the old channel scaling method should be used. - filmNegative.redBase = -1.f; - filmNegative.greenBase = -1.f; - filmNegative.blueBase = -1.f; - if (pedited) { - pedited->filmNegative.baseValues = true; + + filmNegative.colorSpace = FilmNegativeParams::ColorSpace::INPUT; + // Special value -1 used to mean that this should be treated as a v5.8 profile + filmNegative.backCompat = (filmNegative.refInput.r == -1.f) + ? FilmNegativeParams::BackCompat::V1 + : FilmNegativeParams::BackCompat::V2; + + } else { // current version + + assignFromKeyfile(keyFile, "Film Negative", "RefInput", pedited, filmNegative.refInput, pedited->filmNegative.refInput); + assignFromKeyfile(keyFile, "Film Negative", "RefOutput", pedited, filmNegative.refOutput, pedited->filmNegative.refOutput); + + int cs = toUnderlying(filmNegative.colorSpace); + assignFromKeyfile(keyFile, "Film Negative", "ColorSpace", pedited, cs, pedited->filmNegative.colorSpace); + filmNegative.colorSpace = static_cast(cs); + + if (keyFile.has_key("Film Negative", "BackCompat")) { + filmNegative.backCompat = FilmNegativeParams::BackCompat(keyFile.get_integer("Film Negative", "BackCompat")); } + } } diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 39992d88b..0a3377472 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -2184,10 +2184,28 @@ struct FilmNegativeParams { double greenExp; double blueRatio; - double redBase; - double greenBase; - double blueBase; - + struct RGB { + float r, g, b; + + bool operator ==(const RGB& other) const; + bool operator !=(const RGB& other) const; + RGB operator *(const RGB& other) const; + }; + + RGB refInput; + RGB refOutput; + + enum class ColorSpace { + INPUT = 0, + WORKING + // TODO : add support for custom color profile + }; + + ColorSpace colorSpace; + + enum class BackCompat { CURRENT = 0, V1, V2 }; + BackCompat backCompat; + FilmNegativeParams(); bool operator ==(const FilmNegativeParams& other) const; diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 8bfcaa5ab..689dddf20 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -636,6 +636,56 @@ float calculate_scale_mul(float scale_mul[4], const float pre_mul_[4], const flo return gain; } + +void RawImageSource::getWBMults (const ColorTemp &ctemp, const RAWParams &raw, std::array& out_scale_mul, float &autoGainComp, float &rm, float &gm, float &bm) const +{ + // compute channel multipliers + double r, g, b; + //float rm, gm, bm; + + if (ctemp.getTemp() < 0) { + // no white balance, ie revert the pre-process white balance to restore original unbalanced raw camera color + rm = ri->get_pre_mul(0); + gm = ri->get_pre_mul(1); + bm = ri->get_pre_mul(2); + } else { + ctemp.getMultipliers (r, g, b); + rm = imatrices.cam_rgb[0][0] * r + imatrices.cam_rgb[0][1] * g + imatrices.cam_rgb[0][2] * b; + gm = imatrices.cam_rgb[1][0] * r + imatrices.cam_rgb[1][1] * g + imatrices.cam_rgb[1][2] * b; + bm = imatrices.cam_rgb[2][0] * r + imatrices.cam_rgb[2][1] * g + imatrices.cam_rgb[2][2] * b; + } + + // adjust gain so the maximum raw value of the least scaled channel just hits max + const float new_pre_mul[4] = { ri->get_pre_mul(0) / rm, ri->get_pre_mul(1) / gm, ri->get_pre_mul(2) / bm, ri->get_pre_mul(3) / gm }; + float new_scale_mul[4]; + + bool isMono = (ri->getSensorType() == ST_FUJI_XTRANS && raw.xtranssensor.method == RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::MONO)) + || (ri->getSensorType() == ST_BAYER && raw.bayersensor.method == RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::MONO)); + + float c_white[4]; + for (int i = 0; i < 4; ++i) { + c_white[i] = (ri->get_white(i) - cblacksom[i]) / static_cast(raw.expos) + cblacksom[i]; + } + + float gain = calculate_scale_mul(new_scale_mul, new_pre_mul, c_white, cblacksom, isMono, ri->get_colors()); + rm = new_scale_mul[0] / scale_mul[0] * gain; + gm = new_scale_mul[1] / scale_mul[1] * gain; + bm = new_scale_mul[2] / scale_mul[2] * gain; + //fprintf(stderr, "camera gain: %f, current wb gain: %f, diff in stops %f\n", camInitialGain, gain, log2(camInitialGain) - log2(gain)); + + const float expcomp = std::pow(2, ri->getBaselineExposure()); + rm *= expcomp; + gm *= expcomp; + bm *= expcomp; + + out_scale_mul[0] = scale_mul[0]; + out_scale_mul[1] = scale_mul[1]; + out_scale_mul[2] = scale_mul[2]; + out_scale_mul[3] = scale_mul[3]; + + autoGainComp = camInitialGain / initialGain; +} + void RawImageSource::getImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp, const ToneCurveParams &hrp, const RAWParams &raw) { MyMutex::MyLock lock(getImageMutex); diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index 12db6be74..b463ea788 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -124,9 +124,6 @@ public: int load(const Glib::ustring &fname) override { return load(fname, false); } int load(const Glib::ustring &fname, bool firstFrameOnly); void preprocess (const procparams::RAWParams &raw, const procparams::LensProfParams &lensProf, const procparams::CoarseTransformParams& coarse, bool prepareDenoise = true) override; - void filmNegativeProcess (const procparams::FilmNegativeParams ¶ms, std::array& filmBaseValues) override; - bool getFilmNegativeExponents (Coord2D spotA, Coord2D spotB, int tran, const procparams::FilmNegativeParams ¤tParams, std::array& newExps) override; - bool getRawSpotValues(Coord2D spot, int spotSize, int tran, const procparams::FilmNegativeParams ¶ms, std::array& rawValues) override; void demosaic (const procparams::RAWParams &raw, bool autoContrast, double &contrastThreshold, bool cache = false) override; void retinex (const procparams::ColorManagementParams& cmp, const procparams::RetinexParams &deh, const procparams::ToneCurveParams& Tc, LUTf & cdcurve, LUTf & mapcurve, const RetinextransmissionCurve & dehatransmissionCurve, const RetinexgaintransmissionCurve & dehagaintransmissionCurve, multi_array2D &conversionBuffer, bool dehacontlutili, bool mapcontlutili, bool useHsl, float &minCD, float &maxCD, float &mini, float &maxi, float &Tmean, float &Tsigma, float &Tmin, float &Tmax, LUTu &histLRETI) override; void retinexPrepareCurves (const procparams::RetinexParams &retinexParams, LUTf &cdcurve, LUTf &mapcurve, RetinextransmissionCurve &retinextransmissionCurve, RetinexgaintransmissionCurve &retinexgaintransmissionCurve, bool &retinexcontlutili, bool &mapcontlutili, bool &useHsl, LUTu & lhist16RETI, LUTu & histLRETI) override; @@ -147,6 +144,7 @@ public: void getAutoWBMultipliersitc(double &tempref, double &greenref, double &tempitc, double &greenitc, float &studgood, int begx, int begy, int yEn, int xEn, int cx, int cy, int bf_h, int bf_w, double &rm, double &gm, double &bm, const procparams::WBParams & wbpar, const procparams::ColorManagementParams &cmp, const procparams::RAWParams &raw) override; void getrgbloc(int begx, int begy, int yEn, int xEn, int cx, int cy, int bf_h, int bf_w) override; + void getWBMults (const ColorTemp &ctemp, const procparams::RAWParams &raw, std::array& scale_mul, float &autoGainComp, float &rm, float &gm, float &bm) const override; void getImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp, const procparams::ToneCurveParams &hrp, const procparams::RAWParams &raw) override; eSensorType getSensorType () const override; bool isMono () const override; diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index f9832ab9a..c4ffb91f7 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -510,7 +510,7 @@ class FilmNegListener { public: virtual ~FilmNegListener() = default; - virtual void filmBaseValuesChanged(std::array rgb) = 0; + virtual void filmRefValuesChanged(const procparams::FilmNegativeParams::RGB &refInput, const procparams::FilmNegativeParams::RGB &refOutput) = 0; }; /** This class represents a detailed part of the image (looking through a kind of window). @@ -599,9 +599,8 @@ public: virtual bool getAutoWB (double& temp, double& green, double equal, double tempBias) = 0; virtual void getCamWB (double& temp, double& green) = 0; virtual void getSpotWB (int x, int y, int rectSize, double& temp, double& green) = 0; - virtual bool getFilmNegativeExponents(int xA, int yA, int xB, int yB, std::array& newExps) = 0; - virtual bool getRawSpotValues (int x, int y, int spotSize, std::array& rawValues) = 0; - + virtual bool getFilmNegativeSpot(int x, int y, int spotSize, procparams::FilmNegativeParams::RGB &refInput, procparams::FilmNegativeParams::RGB &refOutput) = 0; + virtual void getAutoCrop (double ratio, int &x, int &y, int &w, int &h) = 0; virtual void saveInputICCReference (const Glib::ustring& fname, bool apply_wb) = 0; diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 7d0f40f70..b724992ff 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -603,7 +603,7 @@ Thumbnail* Thumbnail::loadFromRaw (const Glib::ustring& fname, RawMetaDataLocati tpp->defGain = max (scale_mul[0], scale_mul[1], scale_mul[2], scale_mul[3]) / min (scale_mul[0], scale_mul[1], scale_mul[2], scale_mul[3]); tpp->defGain *= std::pow(2, ri->getBaselineExposure()); - tpp->scaleGain = scale_mul[0] / pre_mul[0]; // used to reconstruct scale_mul from filmnegativethumb.cc + tpp->scaleGain = scale_mul[0] / pre_mul[0]; // can be used to reconstruct scale_mul later in processing tpp->gammaCorrected = true; @@ -1192,8 +1192,13 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT Imagefloat* baseImg = resizeTo (rwidth, rheight, interp, thumbImg); - if (isRaw && params.filmNegative.enabled) { - processFilmNegative(params, baseImg, rwidth, rheight); + // Film negative legacy mode, for backwards compatibility RT v5.8 + if (params.filmNegative.enabled) { + if (params.filmNegative.backCompat == FilmNegativeParams::BackCompat::V1) { + processFilmNegative(params, baseImg, rwidth, rheight); + } else if (params.filmNegative.backCompat == FilmNegativeParams::BackCompat::V2) { + processFilmNegativeV2(params, baseImg, rwidth, rheight); + } } if (params.coarse.rotate) { @@ -1235,6 +1240,19 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT // if luma denoise has to be done for thumbnails, it should be right here + int fw = baseImg->getWidth(); + int fh = baseImg->getHeight(); + //ColorTemp::CAT02 (baseImg, ¶ms) ;//perhaps not good! + + ImProcFunctions ipf (¶ms, forHistogramMatching); // enable multithreading when forHistogramMatching is true + ipf.setScale (sqrt (double (fw * fw + fh * fh)) / sqrt (double (thumbImg->getWidth() * thumbImg->getWidth() + thumbImg->getHeight() * thumbImg->getHeight()))*scale); + ipf.updateColorProfiles (ICCStore::getInstance()->getDefaultMonitorProfileName(), RenderingIntent(settings->monitorIntent), false, false); + + // Process film negative BEFORE colorspace conversion, if needed + if (params.filmNegative.enabled && params.filmNegative.backCompat == FilmNegativeParams::BackCompat::CURRENT && params.filmNegative.colorSpace == FilmNegativeParams::ColorSpace::INPUT) { + ipf.filmNegativeProcess(baseImg, baseImg, params.filmNegative); + } + // perform color space transformation if (isRaw) { @@ -1244,13 +1262,10 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT StdImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, thumbImg->getSampleFormat()); } - int fw = baseImg->getWidth(); - int fh = baseImg->getHeight(); - //ColorTemp::CAT02 (baseImg, ¶ms) ;//perhaps not good! - - ImProcFunctions ipf (¶ms, forHistogramMatching); // enable multithreading when forHistogramMatching is true - ipf.setScale (sqrt (double (fw * fw + fh * fh)) / sqrt (double (thumbImg->getWidth() * thumbImg->getWidth() + thumbImg->getHeight() * thumbImg->getHeight()))*scale); - ipf.updateColorProfiles (ICCStore::getInstance()->getDefaultMonitorProfileName(), RenderingIntent(settings->monitorIntent), false, false); + // Process film negative AFTER colorspace conversion, if needed + if (params.filmNegative.enabled && params.filmNegative.backCompat == FilmNegativeParams::BackCompat::CURRENT && params.filmNegative.colorSpace != FilmNegativeParams::ColorSpace::INPUT) { + ipf.filmNegativeProcess(baseImg, baseImg, params.filmNegative); + } LUTu hist16 (65536); diff --git a/rtengine/rtthumbnail.h b/rtengine/rtthumbnail.h index a0033d35f..6ec1dfb34 100644 --- a/rtengine/rtthumbnail.h +++ b/rtengine/rtthumbnail.h @@ -78,6 +78,7 @@ class Thumbnail double scaleGain; void processFilmNegative(const procparams::ProcParams& params, const Imagefloat* baseImg, int rwidth, int rheight); + void processFilmNegativeV2(const procparams::ProcParams& params, const Imagefloat* baseImg, int rwidth, int rheight); public: diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 6f0c3b29a..57e239b66 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -219,16 +219,6 @@ private: imgsrc->setCurrentFrame(params.raw.bayersensor.imageNum); imgsrc->preprocess(params.raw, params.lensProf, params.coarse, params.dirpyrDenoise.enabled); - // After preprocess, run film negative processing if enabled - if ((imgsrc->getSensorType() == ST_BAYER || (imgsrc->getSensorType() == ST_FUJI_XTRANS)) && params.filmNegative.enabled) { - std::array filmBaseValues = { - static_cast(params.filmNegative.redBase), - static_cast(params.filmNegative.greenBase), - static_cast(params.filmNegative.blueBase) - }; - imgsrc->filmNegativeProcess (params.filmNegative, filmBaseValues); - } - if (pl) { pl->setProgress(0.20); } @@ -873,7 +863,23 @@ private: //ImProcFunctions ipf (¶ms, true); ImProcFunctions &ipf = * (ipf_p.get()); - imgsrc->convertColorSpace(baseImg, params.icm, currWB); + if (params.filmNegative.enabled) { + // Process film negative AFTER colorspace conversion if camera space is NOT selected + if (params.filmNegative.colorSpace != FilmNegativeParams::ColorSpace::INPUT) { + imgsrc->convertColorSpace(baseImg, params.icm, currWB); + } + + FilmNegativeParams copy = params.filmNegative; + ipf.filmNegativeProcess(baseImg, baseImg, copy, params.raw, imgsrc, currWB); + + // ... otherwise, process film negative BEFORE colorspace conversion + if (params.filmNegative.colorSpace == FilmNegativeParams::ColorSpace::INPUT) { + imgsrc->convertColorSpace(baseImg, params.icm, currWB); + } + + } else { + imgsrc->convertColorSpace(baseImg, params.icm, currWB); + } // perform first analysis hist16(65536); diff --git a/rtengine/stdimagesource.h b/rtengine/stdimagesource.h index f937188b4..9b95fe34e 100644 --- a/rtengine/stdimagesource.h +++ b/rtengine/stdimagesource.h @@ -57,6 +57,7 @@ public: ~StdImageSource () override; int load (const Glib::ustring &fname) override; + void getWBMults (const ColorTemp &ctemp, const procparams::RAWParams &raw, std::array& scale_mul, float &autoGainComp, float &rm, float &gm, float &bm) const override {}; void getImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp, const procparams::ToneCurveParams &hrp, const procparams::RAWParams &raw) override; void getrgbloc (int begx, int begy, int yEn, int xEn, int cx, int cy, int bf_h, int bf_w) override {}; ColorTemp getWB () const override diff --git a/rtgui/filmnegative.cc b/rtgui/filmnegative.cc index d70c2a067..4df9e9a09 100644 --- a/rtgui/filmnegative.cc +++ b/rtgui/filmnegative.cc @@ -26,13 +26,24 @@ #include "rtimage.h" #include "../rtengine/procparams.h" +#include "../rtengine/color.h" namespace { -Adjuster* createExponentAdjuster(AdjusterListener* listener, const Glib::ustring& label, double minV, double maxV, double defaultVal) +double toAdjuster(double v) { - Adjuster* const adj = Gtk::manage(new Adjuster(label, minV, maxV, 0.001, defaultVal)); + return CLAMP(std::log2(v), 6, 16) - 6; +} + +double fromAdjuster(double v) +{ + return std::pow(2, v + 6); +} + +Adjuster* createExponentAdjuster(AdjusterListener* listener, const Glib::ustring& label, double minV, double maxV, double step, double defaultVal) +{ + Adjuster* const adj = Gtk::manage(new Adjuster(label, minV, maxV, step, defaultVal)); adj->setAdjusterListener(listener); adj->setLogScale(6, 1, true); @@ -42,46 +53,169 @@ Adjuster* createExponentAdjuster(AdjusterListener* listener, const Glib::ustring return adj; } -Glib::ustring formatBaseValues(const std::array& rgb) +Adjuster* createLevelAdjuster(AdjusterListener* listener, const Glib::ustring& label) { - if (rgb[0] <= 0.f && rgb[1] <= 0.f && rgb[2] <= 0.f) { +// Adjuster* const adj = Gtk::manage(new Adjuster(label, 1.0, 65535.0, 1.0, rtengine::MAXVALF / 24.)); + Adjuster* const adj = Gtk::manage(new Adjuster(label, 0.0, 10.0, 0.01, toAdjuster(rtengine::MAXVALF / 24.))); + adj->setAdjusterListener(listener); +// adj->setLogScale(6, 1000.0, true); + + adj->setDelay(std::max(options.adjusterMinDelay, options.adjusterMaxDelay)); + + adj->show(); + return adj; +} + +Adjuster* createBalanceAdjuster(AdjusterListener* listener, const Glib::ustring& label, double minV, double maxV, double defaultVal, + const Glib::ustring& leftIcon, const Glib::ustring& rightIcon) +{ + Adjuster* const adj = Gtk::manage(new Adjuster(label, minV, maxV, 0.01, defaultVal, + Gtk::manage(new RTImage(leftIcon)), Gtk::manage(new RTImage(rightIcon)))); + adj->setAdjusterListener(listener); + adj->setLogScale(9, 0, true); + + adj->setDelay(std::max(options.adjusterMinDelay, options.adjusterMaxDelay)); + + adj->show(); + return adj; +} + + +Glib::ustring fmt(const RGB& rgb) +{ + if (rgb.r <= 0.f && rgb.g <= 0.f && rgb.b <= 0.f) { return "- - -"; } else { - return Glib::ustring::format(std::fixed, std::setprecision(1), rgb[0]) + " " + - Glib::ustring::format(std::fixed, std::setprecision(1), rgb[1]) + " " + - Glib::ustring::format(std::fixed, std::setprecision(1), rgb[2]); + return Glib::ustring::format(std::fixed, std::setprecision(1), rgb.r) + " " + + Glib::ustring::format(std::fixed, std::setprecision(1), rgb.g) + " " + + Glib::ustring::format(std::fixed, std::setprecision(1), rgb.b); } } + +RGB getFilmNegativeExponents(const RGB &ref1, const RGB &ref2) // , const RGB &clearValsOut, const RGB &denseValsOut) +{ + using rtengine::settings; + + RGB clearVals = ref1; + RGB denseVals = ref2; + + // Detect which one is the dense spot, based on green channel + if (clearVals.g < denseVals.g) { + std::swap(clearVals, denseVals); + //std::swap(clearValsOut, denseValsOut); + } + + if (settings->verbose) { + printf("Clear input values: R=%g G=%g B=%g\n", static_cast(clearVals.r), static_cast(clearVals.g), static_cast(clearVals.b)); + printf("Dense input values: R=%g G=%g B=%g\n", static_cast(denseVals.r), static_cast(denseVals.g), static_cast(denseVals.b)); + + // printf("Clear output values: R=%g G=%g B=%g\n", static_cast(clearValsOut.r), static_cast(clearValsOut.g), static_cast(clearValsOut.b)); + // printf("Dense output values: R=%g G=%g B=%g\n", static_cast(denseValsOut.r), static_cast(denseValsOut.g), static_cast(denseValsOut.b)); + } + + const float denseGreenRatio = clearVals.g / denseVals.g; + + // Calculate logarithms in arbitrary base + const auto logBase = + [](float base, float num) -> float + { + return std::log(num) / std::log(base); + }; + + // const auto ratio = + // [](float a, float b) -> float + // { + // return a > b ? a / b : b / a; + // }; + + RGB newExps; + newExps.r = logBase(clearVals.r / denseVals.r, denseGreenRatio); + newExps.g = 1.f; // logBase(ratio(clearVals.g, denseVals.g), ratio(denseValsOut.g, clearValsOut.g) ); + newExps.b = logBase(clearVals.b / denseVals.b, denseGreenRatio); + + + + if (settings->verbose) { + printf("New exponents: R=%g G=%g B=%g\n", static_cast(newExps.r), static_cast(newExps.g), static_cast(newExps.b)); + } + + // // Re-adjust color balance based on dense spot values and new exponents + // calcBalance(rtengine::max(static_cast(params->filmNegative.refInput.g), 1.f), + // -newExps[0], -newExps[1], -newExps[2], + // denseVals[0], denseVals[1], denseVals[2], + // rBal, bBal); + + return newExps; + +} + +void temp2rgb(double outLev, double temp, double green, RGB &refOut) +{ + rtengine::ColorTemp ct = rtengine::ColorTemp(temp, green, 1., "Custom"); + + double rm, gm, bm; + ct.getMultipliers(rm, gm, bm); + + double maxGain = rtengine::max(rm, gm, bm); + + refOut.r = (rm / maxGain) * outLev; + refOut.g = (gm / maxGain) * outLev; + refOut.b = (bm / maxGain) * outLev; +} + + +void rgb2temp(const RGB &refOut, double &outLev, double &temp, double &green) +{ + double maxVal = rtengine::max(refOut.r, refOut.g, refOut.b); + + rtengine::ColorTemp ct = rtengine::ColorTemp( + refOut.r / maxVal, + refOut.g / maxVal, + refOut.b / maxVal, + 1.); + + outLev = maxVal; + temp = ct.getTemp(); + green = ct.getGreen(); +} + + } FilmNegative::FilmNegative() : FoldableToolPanel(this, "filmnegative", M("TP_FILMNEGATIVE_LABEL"), false, true), EditSubscriber(ET_OBJECTS), - evFilmNegativeExponents(ProcEventMapper::getInstance()->newEvent(FIRST, "HISTORY_MSG_FILMNEGATIVE_VALUES")), - evFilmNegativeEnabled(ProcEventMapper::getInstance()->newEvent(FIRST, "HISTORY_MSG_FILMNEGATIVE_ENABLED")), - evFilmBaseValues(ProcEventMapper::getInstance()->newEvent(FIRST, "HISTORY_MSG_FILMNEGATIVE_FILMBASE")), - filmBaseValues({0.f, 0.f, 0.f}), + NEUTRAL_TEMP(rtengine::ColorTemp(1., 1., 1., 1.)), + evFilmNegativeExponents(ProcEventMapper::getInstance()->newEvent(ALLNORAW, "HISTORY_MSG_FILMNEGATIVE_VALUES")), + evFilmNegativeEnabled(ProcEventMapper::getInstance()->newEvent(ALLNORAW, "HISTORY_MSG_FILMNEGATIVE_ENABLED")), + evFilmNegativeRefSpot(ProcEventMapper::getInstance()->newEvent(ALLNORAW, "HISTORY_MSG_FILMNEGATIVE_REF_SPOT")), + evFilmNegativeBalance(ProcEventMapper::getInstance()->newEvent(ALLNORAW, "HISTORY_MSG_FILMNEGATIVE_BALANCE")), + evFilmNegativeColorSpace(ProcEventMapper::getInstance()->newEvent(ALLNORAW, "HISTORY_MSG_FILMNEGATIVE_COLORSPACE")), + refInputValues({0.f, 0.f, 0.f}), + paramsUpgraded(false), fnp(nullptr), - greenExp(createExponentAdjuster(this, M("TP_FILMNEGATIVE_GREEN"), 0.3, 4, 1.5)), // master exponent (green channel) - redRatio(createExponentAdjuster(this, M("TP_FILMNEGATIVE_RED"), 0.3, 3, (2.04 / 1.5))), // ratio of red exponent to master exponent - blueRatio(createExponentAdjuster(this, M("TP_FILMNEGATIVE_BLUE"), 0.3, 3, (1.29 / 1.5))), // ratio of blue exponent to master exponent - spotgrid(Gtk::manage(new Gtk::Grid())), - spotbutton(Gtk::manage(new Gtk::ToggleButton(M("TP_FILMNEGATIVE_PICK")))), - filmBaseLabel(Gtk::manage(new Gtk::Label(M("TP_FILMNEGATIVE_FILMBASE_VALUES"), Gtk::ALIGN_START))), - filmBaseValuesLabel(Gtk::manage(new Gtk::Label("- - -"))), - filmBaseSpotButton(Gtk::manage(new Gtk::ToggleButton(M("TP_FILMNEGATIVE_FILMBASE_PICK")))) + colorSpace(Gtk::manage(new MyComboBoxText())), + greenExp(createExponentAdjuster(this, M("TP_FILMNEGATIVE_GREEN"), 0.3, 4, 0.01, 1.5)), // master exponent (green channel) + redRatio(createExponentAdjuster(this, M("TP_FILMNEGATIVE_RED"), 0.3, 5, 0.01, (2.04 / 1.5))), // ratio of red exponent to master exponent + blueRatio(createExponentAdjuster(this, M("TP_FILMNEGATIVE_BLUE"), 0.3, 5, 0.01, (1.29 / 1.5))), // ratio of blue exponent to master exponent + spotButton(Gtk::manage(new Gtk::ToggleButton(M("TP_FILMNEGATIVE_PICK")))), + refInputLabel(Gtk::manage(new Gtk::Label(Glib::ustring::compose(M("TP_FILMNEGATIVE_REF_LABEL"), "- - -")))), + refSpotButton(Gtk::manage(new Gtk::ToggleButton(M("TP_FILMNEGATIVE_REF_PICK")))), + outputLevel(createLevelAdjuster(this, M("TP_FILMNEGATIVE_OUT_LEVEL"))), // ref level + greenBalance(createBalanceAdjuster(this, M("TP_FILMNEGATIVE_GREENBALANCE"), -3.0, 3.0, 0.0, "circle-magenta-small.png", "circle-green-small.png")), // green balance + blueBalance(createBalanceAdjuster(this, M("TP_FILMNEGATIVE_BLUEBALANCE"), -3.0, 3.0, 0.0, "circle-blue-small.png", "circle-yellow-small.png")) // blue balance { - spotgrid->get_style_context()->add_class("grid-spacing"); - setExpandAlignProperties(spotgrid, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + setExpandAlignProperties(spotButton, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + spotButton->get_style_context()->add_class("independent"); + spotButton->set_tooltip_text(M("TP_FILMNEGATIVE_GUESS_TOOLTIP")); + spotButton->set_image(*Gtk::manage(new RTImage("color-picker-small.png"))); - setExpandAlignProperties(spotbutton, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); - spotbutton->get_style_context()->add_class("independent"); - spotbutton->set_tooltip_text(M("TP_FILMNEGATIVE_GUESS_TOOLTIP")); - spotbutton->set_image(*Gtk::manage(new RTImage("color-picker-small.png"))); + refSpotButton->set_tooltip_text(M("TP_FILMNEGATIVE_REF_TOOLTIP")); - filmBaseSpotButton->set_tooltip_text(M("TP_FILMNEGATIVE_FILMBASE_TOOLTIP")); - setExpandAlignProperties(filmBaseValuesLabel, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); + setExpandAlignProperties(refInputLabel, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER); +// refInputLabel->set_justify(Gtk::Justification::JUSTIFY_CENTER); +// refInputLabel->set_line_wrap(true); // TODO make spot size configurable ? @@ -98,30 +232,53 @@ FilmNegative::FilmNegative() : // spotsize->set_active(0); // spotsize->append ("4"); - spotgrid->attach(*spotbutton, 0, 1, 1, 1); + // spotgrid->attach(*spotButton, 0, 1, 1, 1); // spotgrid->attach (*slab, 1, 0, 1, 1); // spotgrid->attach (*wbsizehelper, 2, 0, 1, 1); + colorSpace->append(M("TP_FILMNEGATIVE_COLORSPACE_INPUT")); + colorSpace->append(M("TP_FILMNEGATIVE_COLORSPACE_WORKING")); + setExpandAlignProperties(colorSpace, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER); + colorSpace->set_tooltip_markup(M("TP_FILMNEGATIVE_COLORSPACE_TOOLTIP")); + + Gtk::Grid* csGrid = Gtk::manage(new Gtk::Grid()); + Gtk::Label* csLabel = Gtk::manage(new Gtk::Label(M("TP_FILMNEGATIVE_COLORSPACE"))); + csGrid->attach(*csLabel, 0, 0, 1, 1); + csGrid->attach(*colorSpace, 1, 0, 1, 1); + + pack_start(*csGrid); + + colorSpace->set_active((int)ColorSpace::WORKING); + colorSpace->signal_changed().connect(sigc::mem_fun(*this, &FilmNegative::colorSpaceChanged)); + colorSpace->show(); + pack_start(*greenExp, Gtk::PACK_SHRINK, 0); pack_start(*redRatio, Gtk::PACK_SHRINK, 0); pack_start(*blueRatio, Gtk::PACK_SHRINK, 0); - pack_start(*spotgrid, Gtk::PACK_SHRINK, 0); + pack_start(*spotButton, Gtk::PACK_SHRINK, 0); + +// pack_start(*oldMethod, Gtk::PACK_SHRINK, 0); Gtk::HSeparator* const sep = Gtk::manage(new Gtk::HSeparator()); sep->get_style_context()->add_class("grid-row-separator"); pack_start(*sep, Gtk::PACK_SHRINK, 0); - Gtk::Grid* const fbGrid = Gtk::manage(new Gtk::Grid()); - fbGrid->attach(*filmBaseLabel, 0, 0, 1, 1); - fbGrid->attach(*filmBaseValuesLabel, 1, 0, 1, 1); - pack_start(*fbGrid, Gtk::PACK_SHRINK, 0); +// Gtk::Grid* const fbGrid = Gtk::manage(new Gtk::Grid()); +// fbGrid->attach(*refInputLabel, 0, 0, 1, 1); +// fbGrid->attach(*filmBaseValuesLabel, 1, 0, 1, 1); +// pack_start(*fbGrid, Gtk::PACK_SHRINK, 0); + pack_start(*refInputLabel, Gtk::PACK_SHRINK, 0); - pack_start(*filmBaseSpotButton, Gtk::PACK_SHRINK, 0); + pack_start(*outputLevel, Gtk::PACK_SHRINK, 0); + pack_start(*blueBalance, Gtk::PACK_SHRINK, 0); + pack_start(*greenBalance, Gtk::PACK_SHRINK, 0); - spotbutton->signal_toggled().connect(sigc::mem_fun(*this, &FilmNegative::editToggled)); + pack_start(*refSpotButton, Gtk::PACK_SHRINK, 0); + + spotButton->signal_toggled().connect(sigc::mem_fun(*this, &FilmNegative::editToggled)); // spotsize->signal_changed().connect( sigc::mem_fun(*this, &WhiteBalance::spotSizeChanged) ); - filmBaseSpotButton->signal_toggled().connect(sigc::mem_fun(*this, &FilmNegative::baseSpotToggled)); + refSpotButton->signal_toggled().connect(sigc::mem_fun(*this, &FilmNegative::refSpotToggled)); // Editing geometry; create the spot rectangle Rectangle* const spotRect = new Rectangle(); @@ -139,6 +296,8 @@ FilmNegative::FilmNegative() : FilmNegative::~FilmNegative() { + idle_register.destroy(); + for (auto geometry : visibleGeometry) { delete geometry; } @@ -148,6 +307,26 @@ FilmNegative::~FilmNegative() } } + +void FilmNegative::readOutputSliders(RGB &refOut) +{ + temp2rgb(fromAdjuster(outputLevel->getValue()), + NEUTRAL_TEMP.getTemp() / std::pow(2., blueBalance->getValue()), + NEUTRAL_TEMP.getGreen() / std::pow(2., greenBalance->getValue()), + refOut); +} + +void FilmNegative::writeOutputSliders(const RGB &refOut) +{ + double outLev, cTemp, green; + rgb2temp(refOut, outLev, cTemp, green); + + outputLevel->setValue(toAdjuster(outLev)); + blueBalance->setValue(std::log2(NEUTRAL_TEMP.getTemp() / cTemp)); + greenBalance->setValue(std::log2(NEUTRAL_TEMP.getGreen() / green)); +} + + void FilmNegative::read(const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited) { disableListener(); @@ -156,27 +335,54 @@ void FilmNegative::read(const rtengine::procparams::ProcParams* pp, const Params redRatio->setEditedState(pedited->filmNegative.redRatio ? Edited : UnEdited); greenExp->setEditedState(pedited->filmNegative.greenExp ? Edited : UnEdited); blueRatio->setEditedState(pedited->filmNegative.blueRatio ? Edited : UnEdited); + outputLevel->setEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); + greenBalance->setEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); + blueBalance->setEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); set_inconsistent(multiImage && !pedited->filmNegative.enabled); } setEnabled(pp->filmNegative.enabled); + + colorSpace->set_active(CLAMP((int)pp->filmNegative.colorSpace, 0, 1)); redRatio->setValue(pp->filmNegative.redRatio); greenExp->setValue(pp->filmNegative.greenExp); blueRatio->setValue(pp->filmNegative.blueRatio); - filmBaseValues[0] = pp->filmNegative.redBase; - filmBaseValues[1] = pp->filmNegative.greenBase; - filmBaseValues[2] = pp->filmNegative.blueBase; + refInputValues = pp->filmNegative.refInput; - // If base values are not set in params, estimated values will be passed in later + // If reference input values are not set in params, estimated values will be passed in later // (after processing) via FilmNegListener - filmBaseValuesLabel->set_text(formatBaseValues(filmBaseValues)); + refInputLabel->set_markup( + Glib::ustring::compose(M("TP_FILMNEGATIVE_REF_LABEL"), fmt(refInputValues))); + + if (pp->filmNegative.backCompat == BackCompat::CURRENT) { + outputLevel->show(); + blueBalance->show(); + greenBalance->show(); + } else { + outputLevel->hide(); + blueBalance->hide(); + greenBalance->hide(); + } + + // If reference output values are not set in params, set the default output + // chosen for median estimation: gray 1/24th of max + if (pp->filmNegative.refOutput.r <= 0) { + float gray = rtengine::MAXVALF / 24.f; + writeOutputSliders({gray, gray, gray}); + } else { + writeOutputSliders(pp->filmNegative.refOutput); + } enableListener(); } void FilmNegative::write(rtengine::procparams::ProcParams* pp, ParamsEdited* pedited) { + if (colorSpace->get_active_row_number() != 3) { // UNCHANGED entry, see setBatchMode + pp->filmNegative.colorSpace = rtengine::procparams::FilmNegativeParams::ColorSpace(colorSpace->get_active_row_number()); + } + pp->filmNegative.redRatio = redRatio->getValue(); pp->filmNegative.greenExp = greenExp->getValue(); pp->filmNegative.blueRatio = blueRatio->getValue(); @@ -184,18 +390,23 @@ void FilmNegative::write(rtengine::procparams::ProcParams* pp, ParamsEdited* ped pp->filmNegative.enabled = getEnabled(); if (pedited) { + pedited->filmNegative.colorSpace = colorSpace->get_active_row_number() != 3; // UNCHANGED entry, see setBatchMode pedited->filmNegative.redRatio = redRatio->getEditedState(); pedited->filmNegative.greenExp = greenExp->getEditedState(); pedited->filmNegative.blueRatio = blueRatio->getEditedState(); - pedited->filmNegative.baseValues = filmBaseValues[0] != pp->filmNegative.redBase - || filmBaseValues[1] != pp->filmNegative.greenBase - || filmBaseValues[2] != pp->filmNegative.blueBase; + pedited->filmNegative.refOutput = outputLevel->getEditedState() || greenBalance->getEditedState() || blueBalance->getEditedState(); + // In batch mode, make sure refinput is always updated together with the balance sliders + pedited->filmNegative.refInput = pedited->filmNegative.refOutput || (refInputValues != pp->filmNegative.refInput); pedited->filmNegative.enabled = !get_inconsistent(); } - pp->filmNegative.redBase = filmBaseValues[0]; - pp->filmNegative.greenBase = filmBaseValues[1]; - pp->filmNegative.blueBase = filmBaseValues[2]; + pp->filmNegative.refInput = refInputValues; + + readOutputSliders(pp->filmNegative.refOutput); + + if (paramsUpgraded) { + pp->filmNegative.backCompat = BackCompat::CURRENT; + } } @@ -205,44 +416,70 @@ void FilmNegative::setDefaults(const rtengine::procparams::ProcParams* defParams greenExp->setValue(defParams->filmNegative.greenExp); blueRatio->setValue(defParams->filmNegative.blueRatio); + float gray = rtengine::MAXVALF / 24.f; + writeOutputSliders({gray, gray, gray}); + if (pedited) { redRatio->setDefaultEditedState(pedited->filmNegative.redRatio ? Edited : UnEdited); greenExp->setDefaultEditedState(pedited->filmNegative.greenExp ? Edited : UnEdited); blueRatio->setDefaultEditedState(pedited->filmNegative.blueRatio ? Edited : UnEdited); + + outputLevel->setDefaultEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); + greenBalance->setDefaultEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); + blueBalance->setDefaultEditedState(pedited->filmNegative.refOutput ? Edited : UnEdited); } else { redRatio->setDefaultEditedState(Irrelevant); greenExp->setDefaultEditedState(Irrelevant); blueRatio->setDefaultEditedState(Irrelevant); + outputLevel->setDefaultEditedState(Irrelevant); + greenBalance->setDefaultEditedState(Irrelevant); + blueBalance->setDefaultEditedState(Irrelevant); } } void FilmNegative::setBatchMode(bool batchMode) { + ToolPanel::setBatchMode(batchMode); + if (batchMode) { - removeIfThere(this, spotgrid, false); - removeIfThere(this, filmBaseSpotButton, false); - ToolPanel::setBatchMode(batchMode); + removeIfThere(this, spotButton, false); + removeIfThere(this, refSpotButton, false); + colorSpace->append(M("GENERAL_UNCHANGED")); + colorSpace->set_active_text(M("GENERAL_UNCHANGED")); redRatio->showEditedCB(); greenExp->showEditedCB(); blueRatio->showEditedCB(); + removeIfThere(this, refInputLabel, false); + removeIfThere(this, outputLevel, false); + removeIfThere(this, greenBalance, false); + removeIfThere(this, blueBalance, false); } } void FilmNegative::adjusterChanged(Adjuster* a, double newval) { - if (listener) { + if (listener && getEnabled()) { if (a == redRatio || a == greenExp || a == blueRatio) { - if (getEnabled()) { - listener->panelChanged( - evFilmNegativeExponents, - Glib::ustring::compose( - "Ref=%1\nR=%2\nB=%3", - greenExp->getValue(), - redRatio->getValue(), - blueRatio->getValue() - ) - ); - } + listener->panelChanged( + evFilmNegativeExponents, + Glib::ustring::compose( + "Ref=%1\nR=%2\nB=%3", + greenExp->getValue(), + redRatio->getValue(), + blueRatio->getValue() + ) + ); + } else if (a == outputLevel || a == greenBalance || a == blueBalance) { + + listener->panelChanged( + evFilmNegativeBalance, + Glib::ustring::compose( + "Lev=%1 G=%2 B=%3", + outputLevel->getValue(), + greenBalance->getValue(), + blueBalance->getValue() + ) + ); } } } @@ -260,10 +497,37 @@ void FilmNegative::enabledChanged() } } -void FilmNegative::filmBaseValuesChanged(std::array rgb) +void FilmNegative::colorSpaceChanged() { - filmBaseValues = rgb; - filmBaseValuesLabel->set_text(formatBaseValues(filmBaseValues)); + if (listener) { + listener->panelChanged(evFilmNegativeColorSpace, colorSpace->get_active_text()); + } +} + +void FilmNegative::filmRefValuesChanged(const RGB &refInput, const RGB &refOutput) +{ + + idle_register.add( + [this, refInput, refOutput]() -> bool { + refInputValues = refInput; + paramsUpgraded = true; + + disableListener(); + + refInputLabel->set_markup( + Glib::ustring::compose(M("TP_FILMNEGATIVE_REF_LABEL"), fmt(refInputValues))); + + writeOutputSliders(refOutput); + + outputLevel->show(); + blueBalance->show(); + greenBalance->show(); + + enableListener(); + return false; + } + ); + } void FilmNegative::setFilmNegProvider(FilmNegProvider* provider) @@ -297,7 +561,7 @@ bool FilmNegative::button1Pressed(int modifierKey) EditSubscriber::action = EditSubscriber::Action::NONE; if (listener) { - if (spotbutton->get_active()) { + if (spotButton->get_active()) { refSpotCoords.push_back(provider->posImage); @@ -305,17 +569,23 @@ bool FilmNegative::button1Pressed(int modifierKey) // User has selected 2 reference gray spots. Calculating new exponents // from channel values and updating parameters. - std::array newExps; + RGB ref1, ref2, dummy; + + if (fnp->getFilmNegativeSpot(refSpotCoords[0], 32, ref1, dummy) && + fnp->getFilmNegativeSpot(refSpotCoords[1], 32, ref2, dummy)) { - if (fnp->getFilmNegativeExponents(refSpotCoords[0], refSpotCoords[1], newExps)) { disableListener(); + + RGB newExps = getFilmNegativeExponents(ref1, ref2); + // Leaving green exponent unchanged, setting red and blue exponents based on // the ratios between newly calculated exponents. - redRatio->setValue(newExps[0] / newExps[1]); - blueRatio->setValue(newExps[2] / newExps[1]); + redRatio->setValue(newExps.r / newExps.g); + blueRatio->setValue(newExps.b / newExps.g); + enableListener(); - if (listener && getEnabled()) { + if (getEnabled()) { listener->panelChanged( evFilmNegativeExponents, Glib::ustring::compose( @@ -326,32 +596,37 @@ bool FilmNegative::button1Pressed(int modifierKey) ) ); } + } switchOffEditMode(); + } - } else if (filmBaseSpotButton->get_active()) { - std::array newBaseLev; + } else if (refSpotButton->get_active()) { - if (fnp->getRawSpotValues(provider->posImage, 32, newBaseLev)) { - disableListener(); + RGB refOut; + fnp->getFilmNegativeSpot(provider->posImage, 32, refInputValues, refOut); - filmBaseValues = newBaseLev; + disableListener(); - enableListener(); + float gray = rtengine::Color::rgbLuminance(refOut.r, refOut.g, refOut.b); + writeOutputSliders({gray, gray, gray}); - const Glib::ustring vs = formatBaseValues(filmBaseValues); + refInputLabel->set_text( + Glib::ustring::compose(M("TP_FILMNEGATIVE_REF_LABEL"), fmt(refInputValues))); - filmBaseValuesLabel->set_text(vs); + enableListener(); - if (listener && getEnabled()) { - listener->panelChanged(evFilmBaseValues, vs); - } - } + listener->panelChanged( + evFilmNegativeRefSpot, + Glib::ustring::compose( + "%1, %2, %3", + round(refInputValues.r), round(refInputValues.g), round(refInputValues.b) + ) + ); - switchOffEditMode(); } } @@ -364,19 +639,26 @@ bool FilmNegative::button1Released() return true; } +bool FilmNegative::button3Pressed(int modifierKey) +{ + EditSubscriber::action = EditSubscriber::Action::NONE; + switchOffEditMode(); + return true; +} + void FilmNegative::switchOffEditMode() { refSpotCoords.clear(); unsubscribe(); - spotbutton->set_active(false); - filmBaseSpotButton->set_active(false); + spotButton->set_active(false); + refSpotButton->set_active(false); } void FilmNegative::editToggled() { - if (spotbutton->get_active()) { + if (spotButton->get_active()) { - filmBaseSpotButton->set_active(false); + refSpotButton->set_active(false); refSpotCoords.clear(); subscribe(); @@ -394,11 +676,12 @@ void FilmNegative::editToggled() } } -void FilmNegative::baseSpotToggled() + +void FilmNegative::refSpotToggled() { - if (filmBaseSpotButton->get_active()) { + if (refSpotButton->get_active()) { - spotbutton->set_active(false); + spotButton->set_active(false); refSpotCoords.clear(); subscribe(); @@ -410,6 +693,7 @@ void FilmNegative::baseSpotToggled() // This is to make sure the getCursor() call is fired everywhere. Rectangle* const imgRect = static_cast(mouseOverGeometry.at(0)); imgRect->setXYWH(0, 0, w, h); + } else { refSpotCoords.clear(); unsubscribe(); diff --git a/rtgui/filmnegative.h b/rtgui/filmnegative.h index 0810a8c57..e27a38e09 100644 --- a/rtgui/filmnegative.h +++ b/rtgui/filmnegative.h @@ -27,13 +27,21 @@ #include "guiutils.h" #include "toolpanel.h" +#include "../rtengine/colortemp.h" + +namespace +{ +using RGB = rtengine::procparams::FilmNegativeParams::RGB; +using ColorSpace = rtengine::procparams::FilmNegativeParams::ColorSpace; +using BackCompat = rtengine::procparams::FilmNegativeParams::BackCompat; +} + class FilmNegProvider { public: virtual ~FilmNegProvider() = default; - virtual bool getFilmNegativeExponents(rtengine::Coord spotA, rtengine::Coord spotB, std::array& newExps) = 0; - virtual bool getRawSpotValues(rtengine::Coord spot, int spotSize, std::array& rawValues) = 0; + virtual bool getFilmNegativeSpot(rtengine::Coord spot, int spotSize, RGB &refInput, RGB &refOutput) = 0; }; class FilmNegative final : @@ -54,8 +62,9 @@ public: void adjusterChanged(Adjuster* a, double newval) override; void enabledChanged() override; + void colorSpaceChanged(); - void filmBaseValuesChanged(std::array rgb) override; + void filmRefValuesChanged(const RGB &refInput, const RGB &refOutput) override; void setFilmNegProvider(FilmNegProvider* provider); @@ -66,31 +75,47 @@ public: bool mouseOver(int modifierKey) override; bool button1Pressed(int modifierKey) override; bool button1Released() override; + bool button3Pressed(int modifierKey) override; void switchOffEditMode() override; private: void editToggled(); - void baseSpotToggled(); + void refSpotToggled(); + + void readOutputSliders(RGB &refOutput); + void writeOutputSliders(const RGB &refOutput); + + // ColorTemp value corresponding to neutral RGB multipliers (1,1,1). Should be around 6500K. + const rtengine::ColorTemp NEUTRAL_TEMP; const rtengine::ProcEvent evFilmNegativeExponents; const rtengine::ProcEvent evFilmNegativeEnabled; - const rtengine::ProcEvent evFilmBaseValues; + const rtengine::ProcEvent evFilmNegativeRefSpot; + const rtengine::ProcEvent evFilmNegativeBalance; + const rtengine::ProcEvent evFilmNegativeColorSpace; std::vector refSpotCoords; - std::array filmBaseValues; + RGB refInputValues; + bool paramsUpgraded; FilmNegProvider* fnp; + MyComboBoxText* const colorSpace; + Adjuster* const greenExp; Adjuster* const redRatio; Adjuster* const blueRatio; - Gtk::Grid* const spotgrid; - Gtk::ToggleButton* const spotbutton; + Gtk::ToggleButton* const spotButton; - Gtk::Label* const filmBaseLabel; - Gtk::Label* const filmBaseValuesLabel; - Gtk::ToggleButton* const filmBaseSpotButton; + Gtk::Label* const refInputLabel; + Gtk::ToggleButton* const refSpotButton; + + Adjuster* const outputLevel; + Adjuster* const greenBalance; + Adjuster* const blueBalance; + + IdleRegister idle_register; }; diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 2543a25f7..611086ef3 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -676,7 +676,9 @@ void ParamsEdited::set(bool v) filmNegative.redRatio = v; filmNegative.greenExp = v; filmNegative.blueRatio = v; - filmNegative.baseValues = v; + filmNegative.refInput = v; + filmNegative.refOutput = v; + filmNegative.colorSpace = v; raw.preprocessWB.mode = v; exif = v; @@ -1865,9 +1867,9 @@ void ParamsEdited::initFrom(const std::vector& filmNegative.redRatio = filmNegative.redRatio && p.filmNegative.redRatio == other.filmNegative.redRatio; filmNegative.greenExp = filmNegative.greenExp && p.filmNegative.greenExp == other.filmNegative.greenExp; filmNegative.blueRatio = filmNegative.blueRatio && p.filmNegative.blueRatio == other.filmNegative.blueRatio; - filmNegative.baseValues = filmNegative.baseValues && p.filmNegative.redBase == other.filmNegative.redBase - && p.filmNegative.greenBase == other.filmNegative.greenBase - && p.filmNegative.blueBase == other.filmNegative.blueBase; + filmNegative.refInput = filmNegative.refInput && p.filmNegative.refInput == other.filmNegative.refInput; + filmNegative.refOutput = filmNegative.refOutput && p.filmNegative.refOutput == other.filmNegative.refOutput; + filmNegative.colorSpace = filmNegative.colorSpace && p.filmNegative.colorSpace == other.filmNegative.colorSpace; raw.preprocessWB.mode = raw.preprocessWB.mode && p.raw.preprocessWB.mode == other.raw.preprocessWB.mode; // How the hell can we handle that??? @@ -6254,12 +6256,21 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng toEdit.filmNegative.blueRatio = mods.filmNegative.blueRatio; } - if (filmNegative.baseValues) { - toEdit.filmNegative.redBase = mods.filmNegative.redBase; - toEdit.filmNegative.greenBase = mods.filmNegative.greenBase; - toEdit.filmNegative.blueBase = mods.filmNegative.blueBase; + if (filmNegative.refInput) { + toEdit.filmNegative.refInput = mods.filmNegative.refInput; } + if (filmNegative.refOutput) { + toEdit.filmNegative.refOutput = mods.filmNegative.refOutput; + } + + if (filmNegative.colorSpace) { + toEdit.filmNegative.colorSpace = mods.filmNegative.colorSpace; + } + + // BackCompat param cannot be managed via the GUI, and it's always copied + toEdit.filmNegative.backCompat = mods.filmNegative.backCompat; + if (raw.preprocessWB.mode) { toEdit.raw.preprocessWB.mode = mods.raw.preprocessWB.mode; } @@ -6310,7 +6321,7 @@ bool RetinexParamsEdited::isUnchanged() const bool FilmNegativeParamsEdited::isUnchanged() const { - return enabled && redRatio && greenExp && blueRatio && baseValues; + return enabled && redRatio && greenExp && blueRatio && refInput && refOutput && colorSpace; } LocallabParamsEdited::LocallabSpotEdited::LocallabSpotEdited(bool v) : diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 523999e67..bfbc00a25 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -1303,7 +1303,9 @@ struct FilmNegativeParamsEdited { bool redRatio; bool greenExp; bool blueRatio; - bool baseValues; + bool refInput; + bool refOutput; + bool colorSpace; bool isUnchanged() const; }; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index 6808a2cc6..83a3b7cc0 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -355,6 +355,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[2]->pack_start (*blackwhite, Gtk::PACK_SHRINK, 2); vboxes[2]->pack_start (*hsveq, Gtk::PACK_SHRINK, 2); vboxes[2]->pack_start (*filmSimulation, Gtk::PACK_SHRINK, 2); + vboxes[2]->pack_start (*filmNegative, Gtk::PACK_SHRINK, 2); vboxes[2]->pack_start (*softlight, Gtk::PACK_SHRINK, 2); vboxes[2]->pack_start (*rgbcurves, Gtk::PACK_SHRINK, 2); vboxes[2]->pack_start (*colortoning, Gtk::PACK_SHRINK, 2); @@ -425,7 +426,6 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren vboxes[8]->pack_start (*raw_caredblue, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*raw_ca_avoid_colourshift, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*Gtk::manage (new Gtk::HSeparator ()), Gtk::PACK_SHRINK, 0); - vboxes[8]->pack_start (*filmNegative, Gtk::PACK_SHRINK, 2); vboxes[8]->pack_start (*captureSharpening, Gtk::PACK_SHRINK, 2); Gtk::VBox* vbCol1 = Gtk::manage (new Gtk::VBox ()); @@ -655,7 +655,6 @@ void PartialPasteDlg::rawToggled () ConnectionBlocker raw_ca_autocorrectBlocker(raw_ca_autocorrectConn); ConnectionBlocker raw_caredblueBlocker(raw_caredblueConn); ConnectionBlocker raw_ca_avoid_colourshiftBlocker(raw_ca_avoid_colourshiftconn); - ConnectionBlocker filmNegativeBlocker(filmNegativeConn); ConnectionBlocker captureSharpeningBlocker(captureSharpeningConn); ConnectionBlocker raw_preprocwbBlocker(raw_preprocwbConn); @@ -686,7 +685,6 @@ void PartialPasteDlg::rawToggled () raw_ca_autocorrect->set_active (raw->get_active ()); raw_caredblue->set_active (raw->get_active ()); raw_ca_avoid_colourshift->set_active (raw->get_active ()); - filmNegative->set_active (raw->get_active()); captureSharpening->set_active (raw->get_active()); raw_preprocwb->set_active (raw->get_active()); } @@ -764,6 +762,7 @@ void PartialPasteDlg::colorToggled () ConnectionBlocker chmixerbwBlocker(chmixerbwConn); ConnectionBlocker hsveqBlocker(hsveqConn); ConnectionBlocker filmSimulationBlocker(filmSimulationConn); + ConnectionBlocker filmNegativeBlocker(filmNegativeConn); ConnectionBlocker softlightBlocker(softlightConn); ConnectionBlocker rgbcurvesBlocker(rgbcurvesConn); ConnectionBlocker colortoningBlocker(colortoningConn); @@ -776,6 +775,7 @@ void PartialPasteDlg::colorToggled () blackwhite->set_active (color->get_active ()); hsveq->set_active (color->get_active ()); filmSimulation->set_active (color->get_active ()); + filmNegative->set_active (color->get_active()); softlight->set_active (color->get_active ()); rgbcurves->set_active (color->get_active ()); colortoning->set_active(color->get_active ()); @@ -1178,7 +1178,9 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.filmNegative.redRatio = falsePE.filmNegative.redRatio; filterPE.filmNegative.greenExp = falsePE.filmNegative.greenExp; filterPE.filmNegative.blueRatio = falsePE.filmNegative.blueRatio; - filterPE.filmNegative.baseValues = falsePE.filmNegative.baseValues; + filterPE.filmNegative.refInput = falsePE.filmNegative.refInput; + filterPE.filmNegative.refOutput = falsePE.filmNegative.refOutput; + filterPE.filmNegative.colorSpace = falsePE.filmNegative.colorSpace; } if (!captureSharpening->get_active ()) { diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index cd24897a5..0b4ffc24b 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -99,7 +99,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit bayerrawexposure = Gtk::manage(new BayerRAWExposure()); xtransrawexposure = Gtk::manage(new XTransRAWExposure()); fattal = Gtk::manage(new FattalToneMapping()); - filmNegative = Gtk::manage (new FilmNegative ()); + filmNegative = Gtk::manage (new FilmNegative()); pdSharpening = Gtk::manage (new PdSharpening()); // So Demosaic, Line noise filter, Green Equilibration, Ca-Correction (garder le nom de section identique!) and Black-Level will be moved in a "Bayer sensor" tool, // and a separate Demosaic and Black Level tool will be created in an "X-Trans sensor" tool @@ -123,6 +123,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit addfavoritePanel (detailsPanel, sharpenMicro); addfavoritePanel (colorPanel, hsvequalizer); addfavoritePanel (colorPanel, filmSimulation); + addfavoritePanel (colorPanel, filmNegative); addfavoritePanel (colorPanel, softlight); addfavoritePanel (colorPanel, rgbcurves); addfavoritePanel (colorPanel, colortoning); @@ -165,7 +166,6 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit addfavoritePanel (rawPanel, preprocess); addfavoritePanel (rawPanel, darkframe); addfavoritePanel (rawPanel, flatfield); - addfavoritePanel (rawPanel, filmNegative); addfavoritePanel (rawPanel, pdSharpening); int favoriteCount = 0; @@ -286,7 +286,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit distortion->setLensGeomListener(this); crop->setCropPanelListener(this); icm->setICMPanelListener(this); - filmNegative->setFilmNegProvider (this); + filmNegative->setFilmNegProvider(this); toolBar = new ToolBar(); toolBar->setToolBarListener(this); @@ -367,7 +367,6 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::show(); preprocess->FoldableToolPanel::show(); flatfield->FoldableToolPanel::show(); - filmNegative->FoldableToolPanel::show(); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -388,7 +387,6 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::show(); preprocess->FoldableToolPanel::show(); flatfield->FoldableToolPanel::show(); - filmNegative->FoldableToolPanel::show(); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -409,7 +407,6 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::hide(); preprocess->FoldableToolPanel::hide(); flatfield->FoldableToolPanel::show(); - filmNegative->FoldableToolPanel::hide(); pdSharpening->FoldableToolPanel::show(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -430,7 +427,6 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::hide(); preprocess->FoldableToolPanel::hide(); flatfield->FoldableToolPanel::hide(); - filmNegative->FoldableToolPanel::hide(); pdSharpening->FoldableToolPanel::hide(); retinex->FoldableToolPanel::setGrayedOut(false); return false; @@ -452,7 +448,6 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr preprocessWB->FoldableToolPanel::hide(); preprocess->FoldableToolPanel::hide(); flatfield->FoldableToolPanel::hide(); - filmNegative->FoldableToolPanel::hide(); pdSharpening->FoldableToolPanel::hide(); retinex->FoldableToolPanel::setGrayedOut(true); return false; @@ -613,7 +608,7 @@ void ToolPanelCoordinator::profileChange( lParams[1] = *mergedParams; pe.initFrom(lParams); - filterRawRefresh = pe.raw.isUnchanged() && pe.lensProf.isUnchanged() && pe.retinex.isUnchanged() && pe.filmNegative.isUnchanged() && pe.pdsharpening.isUnchanged(); + filterRawRefresh = pe.raw.isUnchanged() && pe.lensProf.isUnchanged() && pe.retinex.isUnchanged() && pe.pdsharpening.isUnchanged(); } *params = *mergedParams; @@ -732,7 +727,7 @@ void ToolPanelCoordinator::initImage(rtengine::StagedImageProcessor* ipc_, bool ipc->setSizeListener(resize); ipc->setLocallabListener(locallab); ipc->setImageTypeListener(this); - ipc->setFilmNegListener (filmNegative); + ipc->setFilmNegListener(filmNegative); flatfield->setShortcutPath(Glib::path_get_dirname(ipc->getInitialImage()->getFileName())); icm->setRawMeta(raw, (const rtengine::FramesData*)pMetaData); @@ -1280,12 +1275,7 @@ void ToolPanelCoordinator::setEditProvider(EditDataProvider *provider) } } -bool ToolPanelCoordinator::getFilmNegativeExponents(rtengine::Coord spotA, rtengine::Coord spotB, std::array& newExps) +bool ToolPanelCoordinator::getFilmNegativeSpot(rtengine::Coord spot, int spotSize, RGB &refInput, RGB &refOutput) { - return ipc && ipc->getFilmNegativeExponents(spotA.x, spotA.y, spotB.x, spotB.y, newExps); -} - -bool ToolPanelCoordinator::getRawSpotValues(rtengine::Coord spot, int spotSize, std::array& rawValues) -{ - return ipc && ipc->getRawSpotValues(spot.x, spot.y, spotSize, rawValues); + return ipc && ipc->getFilmNegativeSpot(spot.x, spot.y, spotSize, refInput, refOutput); } diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 0dcee59eb..b39906459 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -308,8 +308,7 @@ public: Glib::ustring GetCurrentImageFilePath() override; // FilmNegProvider interface - bool getFilmNegativeExponents(rtengine::Coord spotA, rtengine::Coord spotB, std::array& newExps) override; - bool getRawSpotValues(rtengine::Coord spot, int spotSize, std::array& rawValues) override; + bool getFilmNegativeSpot(rtengine::Coord spot, int spotSize, RGB &refInput, RGB &refOutput) override; // rotatelistener interface void straightenRequested () override;