From bb56d73cc8999fac29e61a2914b3bad1f4b18412 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 01:12:13 +0100 Subject: [PATCH 01/15] started working on proof-of-concept histogram matching --- rtdata/languages/default | 2 + rtengine/CMakeLists.txt | 1 + rtengine/histmatching.cc | 159 ++++++++++++++++++++++++++++++++++ rtengine/iccstore.cc | 1 + rtengine/imagesource.h | 7 ++ rtengine/improccoordinator.cc | 14 +++ rtengine/procparams.cc | 8 +- rtengine/procparams.h | 1 + rtengine/rawimagesource.h | 1 + rtengine/rtengine.h | 2 + rtengine/simpleprocess.cc | 12 ++- rtgui/paramsedited.cc | 6 ++ rtgui/paramsedited.h | 1 + rtgui/tonecurve.cc | 99 +++++++++++++++++++++ rtgui/tonecurve.h | 12 +++ 15 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 rtengine/histmatching.cc diff --git a/rtdata/languages/default b/rtdata/languages/default index 38f357d5a..acf03e48b 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -724,6 +724,7 @@ HISTORY_MSG_491;White Balance HISTORY_MSG_492;RGB Curves HISTORY_MSG_493;L*a*b* Adjustments HISTORY_MSG_COLORTONING_LABGRID_VALUE;CT - Color correction +HISTORY_MSG_HISTMATCHING;Auto-matched Tone Curve HISTORY_MSG_LOCALCONTRAST_AMOUNT;Local Contrast - Amount HISTORY_MSG_LOCALCONTRAST_DARKNESS;Local Contrast - Darkness HISTORY_MSG_LOCALCONTRAST_ENABLED;Local Contrast @@ -1552,6 +1553,7 @@ TP_EXPOSURE_CURVEEDITOR1;Tone curve 1 TP_EXPOSURE_CURVEEDITOR2;Tone curve 2 TP_EXPOSURE_CURVEEDITOR2_TOOLTIP;Please refer to the "Exposure > Tone Curves" RawPedia article to learn how to achieve the best results by using two tone curves. TP_EXPOSURE_EXPCOMP;Exposure compensation +TP_EXPOSURE_HISTMATCHING;Auto-matched Tone Curve TP_EXPOSURE_LABEL;Exposure TP_EXPOSURE_SATURATION;Saturation TP_EXPOSURE_TCMODE_FILMLIKE;Film-like diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index 0ed91f23f..080a76410 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -116,6 +116,7 @@ set(RTENGINESOURCEFILES rtlensfun.cc tmo_fattal02.cc iplocalcontrast.cc + histmatching.cc ) if(LENSFUN_HAS_LOAD_DIRECTORY) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc new file mode 100644 index 000000000..a38ad4687 --- /dev/null +++ b/rtengine/histmatching.cc @@ -0,0 +1,159 @@ +/* -*- C++ -*- + * + * This file is part of RawTherapee. + * + * Copyright (c) 2018 Alberto Griggio + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ + +#include "rawimagesource.h" +#include "rtthumbnail.h" +#include "curves.h" +#include "color.h" +#include "rt_math.h" +#include "iccstore.h" +#include "../rtgui/mydiagonalcurve.h" + + +namespace rtengine { + +namespace { + +std::vector getCdf(const IImage8 &img) +{ + std::vector ret(256); + for (int y = 0; y < img.getHeight(); ++y) { + for (int x = 0; x < img.getWidth(); ++x) { + int lum = LIM(0, int(Color::rgbLuminance(float(img.r(y, x)), float(img.g(y, x)), float(img.b(y, x)))), 255); + ++ret[lum]; + } + } + + int sum = 0; + for (size_t i = 0; i < ret.size(); ++i) { + sum += ret[i]; + ret[i] = sum; + } + + return ret; +} + + +int findMatch(int val, const std::vector &cdf, int j) +{ + if (cdf[j] <= val) { + for (; j < cdf.size(); ++j) { + if (cdf[j] == val) { + return j; + } else if (cdf[j] > val) { + return (cdf[j] - val <= val - cdf[j-1] ? j : j-1); + } + } + return 255; + } else { + for (; j >= 0; --j) { + if (cdf[j] == val) { + return j; + } else if (cdf[j] < val) { + return (val - cdf[j] <= cdf[j+1] - val ? j : j+1); + } + } + return 0; + } +} + + +void mappingToCurve(const std::vector &mapping, std::vector &curve) +{ + curve.clear(); + + const int npoints = 20; + int idx = 1; + for (; idx < int(mapping.size()); ++idx) { + if (mapping[idx] >= idx) { + break; + } + } + int step = max(int(mapping.size())/npoints, 1); + + auto coord = [](int v) -> double { return double(v)/255.0; }; + auto doit = + [&](int start, int stop, int step) -> void + { + int prev = start; + for (int i = start; i < stop; ++i) { + int v = mapping[i]; + bool change = i > 0 && v != mapping[i-1]; + int diff = i - prev; + if (change && std::abs(diff - step) <= 1) { + curve.emplace_back(coord(i)); + curve.emplace_back(coord(v)); + prev = i; + } + } + }; + doit(0, idx, idx > step ? step : idx / 2); + doit(idx, int(mapping.size()), step); + if (curve[1] > 0.01) { + curve.insert(curve.begin(), 0.0); + curve.insert(curve.begin(), 0.0); + } + if (curve.back() < 0.99 || (1 - curve[curve.size()-2] > step / 512.0 && curve.back() < coord(mapping.back()))) { + curve.emplace_back(1.0); + curve.emplace_back(coord(mapping.back())); + } + curve.insert(curve.begin(), DCT_Spline); +} + +} // namespace + + +void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) +{ + const int rheight = 200; + RawMetaDataLocation rml; + eSensorType sensor_type; + int w, h; + ProcParams neutral; + std::unique_ptr source; + { + std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); + source.reset(thumb->quickProcessImage(neutral, rheight, TI_Nearest)); + } + std::unique_ptr target; + { + double scale; + std::unique_ptr thumb(Thumbnail::loadFromRaw(getFileName(), rml, sensor_type, w, h, 1, 0.0, false)); + target.reset(thumb->processImage(neutral, sensor_type, rheight, TI_Nearest, getMetaData(), scale)); + } + if (target->getWidth() != source->getWidth() || target->getHeight() != source->getHeight()) { + Image8 *tmp = new Image8(source->getWidth(), source->getHeight()); + target->resizeImgTo(source->getWidth(), source->getHeight(), TI_Nearest, tmp); + target.reset(tmp); + } + std::vector scdf = getCdf(*source); + std::vector tcdf = getCdf(*target); + + std::vector mapping; + int j = 0; + for (size_t i = 0; i < tcdf.size(); ++i) { + j = findMatch(tcdf[i], scdf, j); + mapping.emplace_back(j); + } + + mappingToCurve(mapping, outCurve); +} + +} // namespace rtengine diff --git a/rtengine/iccstore.cc b/rtengine/iccstore.cc index 891fb1600..81d0583ac 100644 --- a/rtengine/iccstore.cc +++ b/rtengine/iccstore.cc @@ -574,6 +574,7 @@ public: void setDefaultMonitorProfileName(const Glib::ustring &name) { + MyMutex::MyLock lock(mutex); defaultMonitorProfile = name; } diff --git a/rtengine/imagesource.h b/rtengine/imagesource.h index 5a71bb532..675243b65 100644 --- a/rtengine/imagesource.h +++ b/rtengine/imagesource.h @@ -137,6 +137,13 @@ public: histGreenRaw.clear(); histBlueRaw.clear(); // only some sources will supply this } + + // for RAW files, compute a tone curve using histogram matching on the embedded thumbnail + virtual void getAutoMatchedToneCurve(std::vector &outCurve) + { + outCurve = { 0.0 }; + } + double getDirPyrDenoiseExpComp ( ) { return dirpyrdenoiseExpComp; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 765c4b704..c3f00300b 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -464,6 +464,20 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) if (aeListener) aeListener->autoExpChanged (params.toneCurve.expcomp, params.toneCurve.brightness, params.toneCurve.contrast, params.toneCurve.black, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh, params.toneCurve.hrenabled); + } else if (params.toneCurve.histmatching) { + imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); + + params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; + params.toneCurve.curve2 = { 0 }; + params.toneCurve.expcomp = 0.0; + params.toneCurve.brightness = 0; + params.toneCurve.contrast = 0; + params.toneCurve.black = 0; + params.toneCurve.hlcompr = 0; + + if (aeListener) { + aeListener->autoMatchedToneCurveChanged(params.toneCurve.curveMode, params.toneCurve.curve); + } } } diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 80041e706..dfce03797 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -327,7 +327,8 @@ ToneCurveParams::ToneCurveParams() : saturation(0), shcompr(50), hlcompr(0), - hlcomprthresh(33) + hlcomprthresh(33), + histmatching(false) { } @@ -349,7 +350,8 @@ bool ToneCurveParams::operator ==(const ToneCurveParams& other) const && saturation == other.saturation && shcompr == other.shcompr && hlcompr == other.hlcompr - && hlcomprthresh == other.hlcomprthresh; + && hlcomprthresh == other.hlcomprthresh + && histmatching == other.histmatching; } bool ToneCurveParams::operator !=(const ToneCurveParams& other) const @@ -2760,6 +2762,7 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->toneCurve.hlcompr, "Exposure", "HighlightCompr", toneCurve.hlcompr, keyFile); saveToKeyfile(!pedited || pedited->toneCurve.hlcomprthresh, "Exposure", "HighlightComprThreshold", toneCurve.hlcomprthresh, keyFile); saveToKeyfile(!pedited || pedited->toneCurve.shcompr, "Exposure", "ShadowCompr", toneCurve.shcompr, keyFile); + saveToKeyfile(!pedited || pedited->toneCurve.histmatching, "Exposure", "HistogramMatching", toneCurve.histmatching, keyFile); // Highlight recovery saveToKeyfile(!pedited || pedited->toneCurve.hrenabled, "HLRecovery", "Enabled", toneCurve.hrenabled, keyFile); @@ -3534,6 +3537,7 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) assignFromKeyfile(keyFile, "Exposure", "Curve", pedited, toneCurve.curve, pedited->toneCurve.curve); assignFromKeyfile(keyFile, "Exposure", "Curve2", pedited, toneCurve.curve2, pedited->toneCurve.curve2); } + assignFromKeyfile(keyFile, "Exposure", "HistogramMatching", pedited, toneCurve.histmatching, pedited->toneCurve.histmatching); } if (keyFile.has_group ("HLRecovery")) { diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 7cc80b313..7cabc3aef 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -280,6 +280,7 @@ struct ToneCurveParams { int shcompr; int hlcompr; // Highlight Recovery's compression int hlcomprthresh; // Highlight Recovery's threshold + bool histmatching; // histogram matching ToneCurveParams(); diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index a374ef06e..a199eb13a 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -185,6 +185,7 @@ public: } void getAutoExpHistogram (LUTu & histogram, int& histcompr); void getRAWHistogram (LUTu & histRedRaw, LUTu & histGreenRaw, LUTu & histBlueRaw); + void getAutoMatchedToneCurve(std::vector &outCurve); DCPProfile *getDCP(const ColorManagementParams &cmp, DCPProfile::ApplyState &as); void convertColorSpace(Imagefloat* image, const ColorManagementParams &cmp, const ColorTemp &wb); diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 873cfd9bc..eea15c7a3 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -294,6 +294,8 @@ public: * @param hlcomprthresh is the new threshold for hlcompr * @param hlrecons set to true if HighLight Reconstruction is enabled */ virtual void autoExpChanged (double brightness, int bright, int contrast, int black, int hlcompr, int hlcomprthresh, bool hlrecons) {} + + virtual void autoMatchedToneCurveChanged(procparams::ToneCurveParams::TcMode curveMode, const std::vector &curve) {} }; class AutoCamListener diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 027600302..01d4922f5 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -749,7 +749,17 @@ private: int aehistcompr; imgsrc->getAutoExpHistogram (aehist, aehistcompr); ipf.getAutoExp (aehist, aehistcompr, params.toneCurve.clip, expcomp, bright, contr, black, hlcompr, hlcomprthresh); - } + } else if (params.toneCurve.histmatching) { + imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); + + params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; + params.toneCurve.curve2 = { 0 }; + params.toneCurve.expcomp = 0.0; + params.toneCurve.brightness = 0; + params.toneCurve.contrast = 0; + params.toneCurve.black = 0; + params.toneCurve.hlcompr = 0; + } // at this stage, we can flush the raw data to free up quite an important amount of memory // commented out because it makes the application crash when batch processing... diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index b124eddfc..13afc6faa 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -49,6 +49,7 @@ void ParamsEdited::set (bool v) toneCurve.expcomp = v; toneCurve.hrenabled = v; toneCurve.method = v; + toneCurve.histmatching = v; retinex.cdcurve = v; retinex.mapcurve = v; retinex.cdHcurve = v; @@ -605,6 +606,7 @@ void ParamsEdited::initFrom (const std::vector toneCurve.expcomp = toneCurve.expcomp && p.toneCurve.expcomp == other.toneCurve.expcomp; toneCurve.hrenabled = toneCurve.hrenabled && p.toneCurve.hrenabled == other.toneCurve.hrenabled; toneCurve.method = toneCurve.method && p.toneCurve.method == other.toneCurve.method; + toneCurve.histmatching = toneCurve.histmatching && p.toneCurve.histmatching == other.toneCurve.histmatching; retinex.cdcurve = retinex.cdcurve && p.retinex.cdcurve == other.retinex.cdcurve; retinex.mapcurve = retinex.mapcurve && p.retinex.mapcurve == other.retinex.mapcurve; retinex.cdHcurve = retinex.cdHcurve && p.retinex.cdHcurve == other.retinex.cdHcurve; @@ -1198,6 +1200,10 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten toEdit.toneCurve.method = mods.toneCurve.method; } + if (toneCurve.histmatching) { + toEdit.toneCurve.histmatching = mods.toneCurve.histmatching; + } + if (retinex.enabled) { toEdit.retinex.enabled = mods.retinex.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 6de5d52e4..dfb85d54b 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -53,6 +53,7 @@ public: bool expcomp; bool hrenabled; bool method; + bool histmatching; }; class RetinexParamsEdited diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index a9dd35466..7b07fbb58 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -22,12 +22,15 @@ #include #include "ppversion.h" #include "edit.h" +#include "eventmapper.h" using namespace rtengine; using namespace rtengine::procparams; ToneCurve::ToneCurve () : FoldableToolPanel(this, "tonecurve", M("TP_EXPOSURE_LABEL")) { + auto m = ProcEventMapper::getInstance(); + EvHistMatching = m->newEvent(AUTOEXP, "HISTORY_MSG_HISTMATCHING"); CurveListener::setMulti(true); @@ -122,6 +125,10 @@ ToneCurve::ToneCurve () : FoldableToolPanel(this, "tonecurve", M("TP_EXPOSURE_LA //----------- Curve 1 ------------------------------ pack_start (*Gtk::manage (new Gtk::HSeparator())); + histmatching = Gtk::manage(new Gtk::ToggleButton(M("TP_EXPOSURE_HISTMATCHING"))); + histmatchconn = histmatching->signal_toggled().connect(sigc::mem_fun(*this, &ToneCurve::histmatchingToggled)); + pack_start(*histmatching, true, true, 2); + toneCurveMode = Gtk::manage (new MyComboBoxText ()); toneCurveMode->append (M("TP_EXPOSURE_TCMODE_STANDARD")); toneCurveMode->append (M("TP_EXPOSURE_TCMODE_WEIGHTEDSTD")); @@ -226,6 +233,8 @@ void ToneCurve::read (const ProcParams* pp, const ParamsEdited* pedited) toneCurveMode->set_active(rtengine::toUnderlying(pp->toneCurve.curveMode)); toneCurveMode2->set_active(rtengine::toUnderlying(pp->toneCurve.curveMode2)); + histmatching->set_active(pp->toneCurve.histmatching); + if (pedited) { expcomp->setEditedState (pedited->toneCurve.expcomp ? Edited : UnEdited); black->setEditedState (pedited->toneCurve.black ? Edited : UnEdited); @@ -248,6 +257,8 @@ void ToneCurve::read (const ProcParams* pp, const ParamsEdited* pedited) if (!pedited->toneCurve.curveMode2) { toneCurveMode2->set_active(6); } + + histmatching->set_inconsistent(!pedited->toneCurve.histmatching); } enaconn.block (true); @@ -343,6 +354,8 @@ void ToneCurve::write (ProcParams* pp, ParamsEdited* pedited) pp->toneCurve.curveMode2 = ToneCurveParams::TcMode::PERCEPTUAL; } + pp->toneCurve.histmatching = histmatching->get_active(); + if (pedited) { pedited->toneCurve.expcomp = expcomp->getEditedState (); pedited->toneCurve.black = black->getEditedState (); @@ -360,6 +373,7 @@ void ToneCurve::write (ProcParams* pp, ParamsEdited* pedited) pedited->toneCurve.curveMode2 = toneCurveMode2->get_active_row_number() != 6; pedited->toneCurve.method = method->get_active_row_number() != 4; pedited->toneCurve.hrenabled = !hrenabled->get_inconsistent(); + pedited->toneCurve.histmatching = !histmatching->get_inconsistent(); } pp->toneCurve.hrenabled = hrenabled->get_active(); @@ -408,6 +422,8 @@ void ToneCurve::hrenabledChanged () autolevels->set_inconsistent (false); } + setHistmatching(false); + if (hrenabled->get_active ()) { listener->panelChanged (EvHREnabled, M("GENERAL_ENABLED")); } else { @@ -419,6 +435,7 @@ void ToneCurve::methodChanged () { if (listener) { + setHistmatching(false); if (hrenabled->get_active ()) { listener->panelChanged (EvHRMethod, method->get_active_text ()); } @@ -471,6 +488,7 @@ void ToneCurve::curveChanged (CurveEditor* ce) { if (listener) { + setHistmatching(false); if (ce == shape) { listener->panelChanged (EvToneCurve1, M("HISTORY_CUSTOMCURVE")); } else if (ce == shape2) { @@ -483,6 +501,7 @@ void ToneCurve::curveMode1Changed () { //if (listener) listener->panelChanged (EvToneCurveMode, toneCurveMode->get_active_text()); if (listener) { + setHistmatching(false); Glib::signal_idle().connect (sigc::mem_fun(*this, &ToneCurve::curveMode1Changed_)); } } @@ -500,6 +519,7 @@ void ToneCurve::curveMode2Changed () { //if (listener) listener->panelChanged (EvToneCurveMode, toneCurveMode->get_active_text()); if (listener) { + setHistmatching(false); Glib::signal_idle().connect (sigc::mem_fun(*this, &ToneCurve::curveMode2Changed_)); } } @@ -544,6 +564,8 @@ void ToneCurve::adjusterChanged (Adjuster* a, double newval) return; } + setHistmatching(false); + Glib::ustring costr; if (a == expcomp) { @@ -580,6 +602,8 @@ void ToneCurve::neutral_pressed () // This method deselects auto levels and HL reconstruction auto // and sets neutral values to params in exposure panel + setHistmatching(false); + if (batchMode) { autolevels->set_inconsistent (false); autoconn.block (true); @@ -617,6 +641,7 @@ void ToneCurve::neutral_pressed () } void ToneCurve::autolevels_toggled () { + setHistmatching(false); if (batchMode) { if (autolevels->get_inconsistent()) { @@ -727,6 +752,7 @@ void ToneCurve::waitForAutoExp () toneCurveMode2->set_sensitive (false); hrenabled->set_sensitive(false); method->set_sensitive(false); + histmatching->set_sensitive(false); } void ToneCurve::autoExpChanged (double expcomp, int bright, int contr, int black, int hlcompr, int hlcomprthresh, bool hlrecons) @@ -766,6 +792,7 @@ void ToneCurve::enableAll () toneCurveMode2->set_sensitive (true); hrenabled->set_sensitive(true); method->set_sensitive(true); + histmatching->set_sensitive(true); } bool ToneCurve::autoExpComputed_ () @@ -858,3 +885,75 @@ void ToneCurve::updateCurveBackgroundHistogram (LUTu & histToneCurve, LUTu & his shape->updateBackgroundHistogram (histToneCurve); } + + +void ToneCurve::setHistmatching(bool enabled) +{ + if (histmatching->get_active()) { + histmatchconn.block(true); + histmatching->set_active(enabled); + histmatchconn.block(false); + histmatching->set_inconsistent(false); + } +} + + +void ToneCurve::histmatchingToggled() +{ + if (listener) { + if (histmatching->get_active()) { + listener->panelChanged(EvHistMatching, M("GENERAL_ENABLED")); + waitForAutoExp(); + } else { + listener->panelChanged(EvHistMatching, M("GENERAL_DISABLED")); + } + } +} + + +void ToneCurve::autoMatchedToneCurveChanged(rtengine::procparams::ToneCurveParams::TcMode curveMode, const std::vector &curve) +{ + nextToneCurveMode = curveMode; + nextToneCurve = curve; + + const auto func = [](gpointer data) -> gboolean { + static_cast(data)->histmatchingComputed(); + + return FALSE; + }; + + idle_register.add(func, this); +} + + +bool ToneCurve::histmatchingComputed() +{ + GThreadLock lock; + disableListener(); + enableAll(); + expcomp->setValue(0); + brightness->setValue(0); + contrast->setValue(0); + black->setValue(0); + hlcompr->setValue(0); + + if (!black->getAddMode()) { + shcompr->set_sensitive(!((int)black->getValue() == 0)); + } + + if (autolevels->get_active() ) { + autoconn.block(true); + autolevels->set_active(false); + autoconn.block(false); + autolevels->set_inconsistent(false); + } + + toneCurveMode->set_active(rtengine::toUnderlying(nextToneCurveMode)); + shape->setCurve(nextToneCurve); + shape2->setCurve({ DCT_Linear }); + shape->openIfNonlinear(); + + enableListener(); + + return false; +} diff --git a/rtgui/tonecurve.h b/rtgui/tonecurve.h index 11ec64b96..5aae5f015 100644 --- a/rtgui/tonecurve.h +++ b/rtgui/tonecurve.h @@ -57,14 +57,18 @@ protected: Adjuster* saturation; MyComboBoxText* toneCurveMode; MyComboBoxText* toneCurveMode2; + Gtk::ToggleButton *histmatching; bool clipDirty, lastAuto; sigc::connection autoconn, neutralconn, tcmodeconn, tcmode2conn; + sigc::connection histmatchconn; CurveEditorGroup* curveEditorG; CurveEditorGroup* curveEditorG2; DiagonalCurveEditor* shape; DiagonalCurveEditor* shape2; + rtengine::ProcEvent EvHistMatching; + // used temporarily in eventing double nextExpcomp; int nextBrightness; @@ -73,6 +77,10 @@ protected: int nextHlcompr; int nextHlcomprthresh; bool nextHLRecons; + rtengine::procparams::ToneCurveParams::TcMode nextToneCurveMode; + std::vector nextToneCurve; + + void setHistmatching(bool enabled); public: ToneCurve (); @@ -107,6 +115,10 @@ public: bool isCurveExpanded (); void updateCurveBackgroundHistogram (LUTu & histToneCurve, LUTu & histLCurve, LUTu & histCCurve,/* LUTu & histCLurve, LUTu & histLLCurve,*/ LUTu & histLCAM, LUTu & histCCAM, LUTu & histRed, LUTu & histGreen, LUTu & histBlue, LUTu & histLuma, LUTu & histLRETI); + void histmatchingToggled(); + void autoMatchedToneCurveChanged(rtengine::procparams::ToneCurveParams::TcMode curveMode, const std::vector &curve); + bool histmatchingComputed(); + void setRaw (bool raw); void hrenabledChanged (); From 52957e9eabc9c56c47e965cdfee0cfd6a4f38a02 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 01:41:28 +0100 Subject: [PATCH 02/15] further experiments with histogram matching --- rtengine/histmatching.cc | 13 ++++++++++--- rtengine/rtthumbnail.cc | 11 ++++++++--- rtengine/rtthumbnail.h | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index a38ad4687..6d8856958 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -79,7 +79,7 @@ void mappingToCurve(const std::vector &mapping, std::vector &curve) { curve.clear(); - const int npoints = 20; + const int npoints = 8; int idx = 1; for (; idx < int(mapping.size()); ++idx) { if (mapping[idx] >= idx) { @@ -133,10 +133,17 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) source.reset(thumb->quickProcessImage(neutral, rheight, TI_Nearest)); } std::unique_ptr target; - { + if (true) { + neutral.icm.working = "RT_sRGB"; + // faster, but has problems likely due to color space transformations that I do not properly understand... double scale; std::unique_ptr thumb(Thumbnail::loadFromRaw(getFileName(), rml, sensor_type, w, h, 1, 0.0, false)); - target.reset(thumb->processImage(neutral, sensor_type, rheight, TI_Nearest, getMetaData(), scale)); + target.reset(thumb->processImage(neutral, sensor_type, rheight, TI_Nearest, getMetaData(), scale, false)); + } else { + ProcessingJob *job = ProcessingJob::create(this, neutral, true); + int err = 0; + std::unique_ptr tmp(processImage(job, err, nullptr, false)); + target.reset(static_cast(tmp.get())->to8()); } if (target->getWidth() != source->getWidth() || target->getHeight() != source->getHeight()) { Image8 *tmp = new Image8(source->getWidth(), source->getHeight()); diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index e84214bd4..59d19cc15 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -915,7 +915,7 @@ IImage8* Thumbnail::quickProcessImage (const procparams::ProcParams& params, int } // Full thumbnail processing, second stage if complete profile exists -IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& myscale) +IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& myscale, bool forMonitor) { unsigned int imgNum = 0; if (isRaw) { @@ -1293,8 +1293,13 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT //ipf.colorCurve (labView, labView); // obtain final image - Image8* readyImg = new Image8 (fw, fh); - ipf.lab2monitorRgb (labView, readyImg); + Image8* readyImg = nullptr; + if (forMonitor) { + readyImg = new Image8 (fw, fh); + ipf.lab2monitorRgb (labView, readyImg); + } else { + readyImg = ipf.lab2rgb(labView, 0, 0, fw, fh, params.icm); + } delete labView; delete baseImg; diff --git a/rtengine/rtthumbnail.h b/rtengine/rtthumbnail.h index 2ee08de50..902f264fc 100644 --- a/rtengine/rtthumbnail.h +++ b/rtengine/rtthumbnail.h @@ -71,7 +71,7 @@ public: void init (); - IImage8* processImage (const procparams::ProcParams& pparams, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& scale); + IImage8* processImage (const procparams::ProcParams& pparams, eSensorType sensorType, int rheight, TypeInterpolation interp, const FramesMetaData *metadata, double& scale, bool forMonitor=true); IImage8* quickProcessImage (const procparams::ProcParams& pparams, int rheight, TypeInterpolation interp); int getImageWidth (const procparams::ProcParams& pparams, int rheight, float &ratio); void getDimensions (int& w, int& h, double& scaleFac); From bc89e24ab7e1f467448a21c795bc67e6495a0c39 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 21:16:22 +0100 Subject: [PATCH 03/15] working version -- yay! --- rtengine/histmatching.cc | 49 ++++++++++++++++++++++------------- rtengine/improccoordinator.cc | 3 ++- rtengine/simpleprocess.cc | 3 ++- rtgui/tonecurve.cc | 14 +++++++--- rtgui/tonecurve.h | 1 + 5 files changed, 46 insertions(+), 24 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 6d8856958..8b506432d 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -25,6 +25,7 @@ #include "rt_math.h" #include "iccstore.h" #include "../rtgui/mydiagonalcurve.h" +#include "improcfun.h" namespace rtengine { @@ -54,7 +55,7 @@ std::vector getCdf(const IImage8 &img) int findMatch(int val, const std::vector &cdf, int j) { if (cdf[j] <= val) { - for (; j < cdf.size(); ++j) { + for (; j < int(cdf.size()); ++j) { if (cdf[j] == val) { return j; } else if (cdf[j] > val) { @@ -123,27 +124,39 @@ void mappingToCurve(const std::vector &mapping, std::vector &curve) void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) { const int rheight = 200; - RawMetaDataLocation rml; - eSensorType sensor_type; - int w, h; ProcParams neutral; + std::unique_ptr target; + { + int tr = TR_NONE; + int fw, fh; + getFullSize(fw, fh, tr); + int skip = fh / rheight; + PreviewProps pp(0, 0, fw, fh, skip); + ColorTemp currWB = getWB(); + std::unique_ptr image(new Imagefloat(int(fw / skip), int(fh / skip))); + neutral.raw.bayersensor.method = procparams::RAWParams::BayerSensor::getMethodString(procparams::RAWParams::BayerSensor::Method::FAST); + neutral.raw.xtranssensor.method = procparams::RAWParams::XTransSensor::getMethodString(procparams::RAWParams::XTransSensor::Method::FAST); + getImage(currWB, tr, image.get(), pp, neutral.toneCurve, neutral.raw); + + // this could probably be made faster -- ideally we would need to just + // perform the transformation from camera space to the output space + // (taking gamma into account), but I couldn't find anything + // ready-made, so for now this will do. Remember the famous quote: + // "premature optimization is the root of all evil" :-) + convertColorSpace(image.get(), neutral.icm, currWB); + ImProcFunctions ipf(&neutral); + LabImage tmplab(image->getWidth(), image->getHeight()); + ipf.rgb2lab(*image, tmplab, neutral.icm.working); + image.reset(ipf.lab2rgbOut(&tmplab, 0, 0, tmplab.W, tmplab.H, neutral.icm)); + target.reset(image->to8()); + } std::unique_ptr source; { + RawMetaDataLocation rml; + eSensorType sensor_type; + int w, h; std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); - source.reset(thumb->quickProcessImage(neutral, rheight, TI_Nearest)); - } - std::unique_ptr target; - if (true) { - neutral.icm.working = "RT_sRGB"; - // faster, but has problems likely due to color space transformations that I do not properly understand... - double scale; - std::unique_ptr thumb(Thumbnail::loadFromRaw(getFileName(), rml, sensor_type, w, h, 1, 0.0, false)); - target.reset(thumb->processImage(neutral, sensor_type, rheight, TI_Nearest, getMetaData(), scale, false)); - } else { - ProcessingJob *job = ProcessingJob::create(this, neutral, true); - int err = 0; - std::unique_ptr tmp(processImage(job, err, nullptr, false)); - target.reset(static_cast(tmp.get())->to8()); + source.reset(thumb->quickProcessImage(neutral, target->getHeight(), TI_Nearest)); } if (target->getWidth() != source->getWidth() || target->getHeight() != source->getHeight()) { Image8 *tmp = new Image8(source->getWidth(), source->getHeight()); diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index c3f00300b..b4edab549 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -466,7 +466,8 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) params.toneCurve.black, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh, params.toneCurve.hrenabled); } else if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); - + + params.toneCurve.histmatching = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; params.toneCurve.expcomp = 0.0; diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 01d4922f5..7b636797e 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -751,7 +751,8 @@ private: ipf.getAutoExp (aehist, aehistcompr, params.toneCurve.clip, expcomp, bright, contr, black, hlcompr, hlcomprthresh); } else if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); - + + params.toneCurve.histmatching = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; params.toneCurve.expcomp = 0.0; diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index 7b07fbb58..0d753dcbf 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -31,6 +31,7 @@ ToneCurve::ToneCurve () : FoldableToolPanel(this, "tonecurve", M("TP_EXPOSURE_LA { auto m = ProcEventMapper::getInstance(); EvHistMatching = m->newEvent(AUTOEXP, "HISTORY_MSG_HISTMATCHING"); + EvHistMatchingBatch = m->newEvent(M_VOID, "HISTORY_MSG_HISTMATCHING"); CurveListener::setMulti(true); @@ -901,11 +902,15 @@ void ToneCurve::setHistmatching(bool enabled) void ToneCurve::histmatchingToggled() { if (listener) { - if (histmatching->get_active()) { - listener->panelChanged(EvHistMatching, M("GENERAL_ENABLED")); - waitForAutoExp(); + if (!batchMode) { + if (histmatching->get_active()) { + listener->panelChanged(EvHistMatching, M("GENERAL_ENABLED")); + waitForAutoExp(); + } else { + listener->panelChanged(EvHistMatching, M("GENERAL_DISABLED")); + } } else { - listener->panelChanged(EvHistMatching, M("GENERAL_DISABLED")); + listener->panelChanged(EvHistMatchingBatch, histmatching->get_active() ? M("GENERAL_ENABLED") : M("GENERAL_DISABLED")); } } } @@ -948,6 +953,7 @@ bool ToneCurve::histmatchingComputed() autolevels->set_inconsistent(false); } + histmatching->set_active(false); toneCurveMode->set_active(rtengine::toUnderlying(nextToneCurveMode)); shape->setCurve(nextToneCurve); shape2->setCurve({ DCT_Linear }); diff --git a/rtgui/tonecurve.h b/rtgui/tonecurve.h index 5aae5f015..e85fefa39 100644 --- a/rtgui/tonecurve.h +++ b/rtgui/tonecurve.h @@ -68,6 +68,7 @@ protected: DiagonalCurveEditor* shape2; rtengine::ProcEvent EvHistMatching; + rtengine::ProcEvent EvHistMatchingBatch; // used temporarily in eventing double nextExpcomp; From 942da71ef099327235236a12a3bde983a67de87c Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 22:02:12 +0100 Subject: [PATCH 04/15] added some verbosity in RawImageSource::getAutoMatchedToneCurve --- rtengine/histmatching.cc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 8b506432d..4be81ad8a 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -26,10 +26,15 @@ #include "iccstore.h" #include "../rtgui/mydiagonalcurve.h" #include "improcfun.h" +#define BENCHMARK +#include "StopWatch.h" +#include namespace rtengine { +extern const Settings *settings; + namespace { std::vector getCdf(const IImage8 &img) @@ -123,6 +128,12 @@ void mappingToCurve(const std::vector &mapping, std::vector &curve) void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) { + BENCHFUN + + if (settings->verbose) { + std::cout << "performing histogram matching for " << getFileName() << " on the embedded thumbnail" << std::endl; + } + const int rheight = 200; ProcParams neutral; std::unique_ptr target; @@ -149,6 +160,10 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) ipf.rgb2lab(*image, tmplab, neutral.icm.working); image.reset(ipf.lab2rgbOut(&tmplab, 0, 0, tmplab.W, tmplab.H, neutral.icm)); target.reset(image->to8()); + + if (settings->verbose) { + std::cout << "histogram matching: generated neutral rendering" << std::endl; + } } std::unique_ptr source; { @@ -157,6 +172,10 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) int w, h; std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); source.reset(thumb->quickProcessImage(neutral, target->getHeight(), TI_Nearest)); + + if (settings->verbose) { + std::cout << "histogram matching: extracted embedded thumbnail" << std::endl; + } } if (target->getWidth() != source->getWidth() || target->getHeight() != source->getHeight()) { Image8 *tmp = new Image8(source->getWidth(), source->getHeight()); @@ -174,6 +193,10 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) } mappingToCurve(mapping, outCurve); + + if (settings->verbose) { + std::cout << "histogram matching: generated curve with " << outCurve.size()/2 << " control points" << std::endl; + } } } // namespace rtengine From 7bcc8ae2369b6b07149a69b001dbc328a7824989 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 22:25:32 +0100 Subject: [PATCH 05/15] removed useless lines --- rtengine/histmatching.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 4be81ad8a..43f73b811 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -145,8 +145,6 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) PreviewProps pp(0, 0, fw, fh, skip); ColorTemp currWB = getWB(); std::unique_ptr image(new Imagefloat(int(fw / skip), int(fh / skip))); - neutral.raw.bayersensor.method = procparams::RAWParams::BayerSensor::getMethodString(procparams::RAWParams::BayerSensor::Method::FAST); - neutral.raw.xtranssensor.method = procparams::RAWParams::XTransSensor::getMethodString(procparams::RAWParams::XTransSensor::Method::FAST); getImage(currWB, tr, image.get(), pp, neutral.toneCurve, neutral.raw); // this could probably be made faster -- ideally we would need to just From 931ee9562049496e8fd8b32ca8a573461d3ed033 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Wed, 17 Jan 2018 23:09:49 +0100 Subject: [PATCH 06/15] use a relative height (10% of the full height) instead of an absolute one in histogram matching --- rtengine/histmatching.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 43f73b811..2aea9f3c8 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -134,14 +134,13 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) std::cout << "performing histogram matching for " << getFileName() << " on the embedded thumbnail" << std::endl; } - const int rheight = 200; ProcParams neutral; std::unique_ptr target; { int tr = TR_NONE; int fw, fh; getFullSize(fw, fh, tr); - int skip = fh / rheight; + int skip = 10; PreviewProps pp(0, 0, fw, fh, skip); ColorTemp currWB = getWB(); std::unique_ptr image(new Imagefloat(int(fw / skip), int(fh / skip))); From abfeb4ca1d01312ffbaacf141350d0d3e59404e6 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 18 Jan 2018 14:17:51 +0100 Subject: [PATCH 07/15] fixed crashes in histogram matching --- rtengine/histmatching.cc | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 2aea9f3c8..cd7352980 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -96,31 +96,35 @@ void mappingToCurve(const std::vector &mapping, std::vector &curve) auto coord = [](int v) -> double { return double(v)/255.0; }; auto doit = - [&](int start, int stop, int step) -> void + [&](int start, int stop, int step, bool addstart) -> void { int prev = start; + if (addstart) { + curve.push_back(coord(start)); + curve.push_back(coord(mapping[start])); + } for (int i = start; i < stop; ++i) { int v = mapping[i]; bool change = i > 0 && v != mapping[i-1]; int diff = i - prev; if (change && std::abs(diff - step) <= 1) { - curve.emplace_back(coord(i)); - curve.emplace_back(coord(v)); + curve.push_back(coord(i)); + curve.push_back(coord(v)); prev = i; } } }; - doit(0, idx, idx > step ? step : idx / 2); - doit(idx, int(mapping.size()), step); - if (curve[1] > 0.01) { - curve.insert(curve.begin(), 0.0); - curve.insert(curve.begin(), 0.0); - } - if (curve.back() < 0.99 || (1 - curve[curve.size()-2] > step / 512.0 && curve.back() < coord(mapping.back()))) { + doit(0, idx, idx > step ? step : idx / 2, true); + doit(idx, int(mapping.size()), step, idx - step > step / 2); + if (curve.size() <= 2 || curve.back() < 0.99 || (1 - curve[curve.size()-2] > step / 512.0 && curve.back() < coord(mapping.back()))) { curve.emplace_back(1.0); curve.emplace_back(coord(mapping.back())); } - curve.insert(curve.begin(), DCT_Spline); + if (curve.size() < 4) { + curve = { DCT_Linear }; // not enough points, fall back to linear + } else { + curve.insert(curve.begin(), DCT_Spline); + } } } // namespace @@ -133,6 +137,8 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) if (settings->verbose) { std::cout << "performing histogram matching for " << getFileName() << " on the embedded thumbnail" << std::endl; } + + outCurve = { DCT_Linear }; ProcParams neutral; std::unique_ptr target; @@ -168,6 +174,12 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) eSensorType sensor_type; int w, h; std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); + if (!thumb) { + if (settings->verbose) { + std::cout << "histogram matching: no thumbnail found, generating a neutral curve" << std::endl; + } + return; + } source.reset(thumb->quickProcessImage(neutral, target->getHeight(), TI_Nearest)); if (settings->verbose) { @@ -186,7 +198,7 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) int j = 0; for (size_t i = 0; i < tcdf.size(); ++i) { j = findMatch(tcdf[i], scdf, j); - mapping.emplace_back(j); + mapping.push_back(j); } mappingToCurve(mapping, outCurve); From 7b3e9f7b7a0ff7cbc4999fa6c8d0809d0aedbee1 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 18 Jan 2018 17:55:14 +0100 Subject: [PATCH 08/15] fixed bad interaction between auto levels and histogram matching --- rtengine/improccoordinator.cc | 3 ++- rtengine/simpleprocess.cc | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 37241bbe0..75fc81e9f 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -445,7 +445,8 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) if (aeListener) aeListener->autoExpChanged (params.toneCurve.expcomp, params.toneCurve.brightness, params.toneCurve.contrast, params.toneCurve.black, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh, params.toneCurve.hrenabled); - } else if (params.toneCurve.histmatching) { + } + if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); params.toneCurve.histmatching = false; diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 2910b6b43..0bd784e45 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -739,7 +739,8 @@ private: int aehistcompr; imgsrc->getAutoExpHistogram (aehist, aehistcompr); ipf.getAutoExp (aehist, aehistcompr, params.toneCurve.clip, expcomp, bright, contr, black, hlcompr, hlcomprthresh); - } else if (params.toneCurve.histmatching) { + } + if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); params.toneCurve.histmatching = false; From 4feb663f633bb3becc23704c0414aa2bd74c7d01 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Thu, 18 Jan 2018 23:36:03 +0100 Subject: [PATCH 09/15] histogram matching: handle the case in which the thumbnail and the raw have different aspect ratios --- rtengine/histmatching.cc | 77 +++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index cd7352980..71bdffc58 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -29,6 +29,7 @@ #define BENCHMARK #include "StopWatch.h" #include +#include namespace rtengine { @@ -139,18 +140,62 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) } outCurve = { DCT_Linear }; - + + int fw, fh; + getFullSize(fw, fh, TR_NONE); + int skip = 10; + + if (settings->verbose) { + std::cout << "histogram matching: full raw image size is " << fw << "x" << fh << std::endl; + } + ProcParams neutral; + + std::unique_ptr source; + { + RawMetaDataLocation rml; + eSensorType sensor_type; + int w, h; + std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); + if (!thumb) { + if (settings->verbose) { + std::cout << "histogram matching: no thumbnail found, generating a neutral curve" << std::endl; + } + return; + } + source.reset(thumb->quickProcessImage(neutral, fh / skip, TI_Nearest)); + + if (settings->verbose) { + std::cout << "histogram matching: extracted embedded thumbnail" << std::endl; + } + } + std::unique_ptr target; - { - int tr = TR_NONE; - int fw, fh; - getFullSize(fw, fh, tr); - int skip = 10; + { + int tw = source->getWidth(), th = source->getHeight(); + float thumb_ratio = float(std::max(tw, th)) / float(std::min(tw, th)); + float target_ratio = float(std::max(fw, fh)) / float(std::min(fw, fh)); + int cx = 0, cy = 0; + if (std::abs(thumb_ratio - target_ratio) > 0.01) { + if (thumb_ratio > target_ratio) { + // crop the height + int ch = fh - (fw * float(th) / float(tw)); + cy += ch / 2; + fh -= ch; + } else { + // crop the width + int cw = fw - (fh * float(tw) / float(th)); + cx += cw / 2; + fw -= cw; + } + if (settings->verbose) { + std::cout << "histogram matching: cropping target to get an aspect ratio of " << std::fixed << std::setprecision(2) << thumb_ratio << ":1, new full size is " << fw << "x" << fh << std::endl; + } + } PreviewProps pp(0, 0, fw, fh, skip); ColorTemp currWB = getWB(); std::unique_ptr image(new Imagefloat(int(fw / skip), int(fh / skip))); - getImage(currWB, tr, image.get(), pp, neutral.toneCurve, neutral.raw); + getImage(currWB, TR_NONE, image.get(), pp, neutral.toneCurve, neutral.raw); // this could probably be made faster -- ideally we would need to just // perform the transformation from camera space to the output space @@ -168,24 +213,6 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) std::cout << "histogram matching: generated neutral rendering" << std::endl; } } - std::unique_ptr source; - { - RawMetaDataLocation rml; - eSensorType sensor_type; - int w, h; - std::unique_ptr thumb(Thumbnail::loadQuickFromRaw(getFileName(), rml, sensor_type, w, h, 1, false, true)); - if (!thumb) { - if (settings->verbose) { - std::cout << "histogram matching: no thumbnail found, generating a neutral curve" << std::endl; - } - return; - } - source.reset(thumb->quickProcessImage(neutral, target->getHeight(), TI_Nearest)); - - if (settings->verbose) { - std::cout << "histogram matching: extracted embedded thumbnail" << std::endl; - } - } if (target->getWidth() != source->getWidth() || target->getHeight() != source->getHeight()) { Image8 *tmp = new Image8(source->getWidth(), source->getHeight()); target->resizeImgTo(source->getWidth(), source->getHeight(), TI_Nearest, tmp); From 8f7639288598b444f5ec38bf11bf4f8dbb39295c Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Fri, 19 Jan 2018 21:43:55 +0100 Subject: [PATCH 10/15] histogram matching: cache the computed tone curve in RawImageSource --- rtengine/histmatching.cc | 11 +++++++++++ rtengine/improccoordinator.cc | 2 +- rtengine/rawimagesource.h | 1 + rtengine/simpleprocess.cc | 2 +- rtgui/tonecurve.cc | 1 - 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 71bdffc58..5b7647f49 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -139,6 +139,14 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) std::cout << "performing histogram matching for " << getFileName() << " on the embedded thumbnail" << std::endl; } + if (!histMatchingCache.empty()) { + if (settings->verbose) { + std::cout << "tone curve found in cache" << std::endl; + outCurve = histMatchingCache; + return; + } + } + outCurve = { DCT_Linear }; int fw, fh; @@ -161,6 +169,7 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) if (settings->verbose) { std::cout << "histogram matching: no thumbnail found, generating a neutral curve" << std::endl; } + histMatchingCache = outCurve; return; } source.reset(thumb->quickProcessImage(neutral, fh / skip, TI_Nearest)); @@ -233,6 +242,8 @@ void RawImageSource::getAutoMatchedToneCurve(std::vector &outCurve) if (settings->verbose) { std::cout << "histogram matching: generated curve with " << outCurve.size()/2 << " control points" << std::endl; } + + histMatchingCache = outCurve; } } // namespace rtengine diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 75fc81e9f..d390d94ca 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -449,7 +449,7 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); - params.toneCurve.histmatching = false; + params.toneCurve.autoexp = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; params.toneCurve.expcomp = 0.0; diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index a199eb13a..59316eccf 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -95,6 +95,7 @@ protected: float psGreenBrightness[4]; float psBlueBrightness[4]; + std::vector histMatchingCache; void hphd_vertical (float** hpmap, int col_from, int col_to); void hphd_horizontal (float** hpmap, int row_from, int row_to); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 0bd784e45..0cc223da1 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -743,7 +743,7 @@ private: if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); - params.toneCurve.histmatching = false; + params.toneCurve.autoexp = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; params.toneCurve.expcomp = 0.0; diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index 0d753dcbf..4a25794ff 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -953,7 +953,6 @@ bool ToneCurve::histmatchingComputed() autolevels->set_inconsistent(false); } - histmatching->set_active(false); toneCurveMode->set_active(rtengine::toUnderlying(nextToneCurveMode)); shape->setCurve(nextToneCurve); shape2->setCurve({ DCT_Linear }); From 1ec4ff64632e612b7424ce5885a7396e42dd54cb Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Sat, 20 Jan 2018 18:41:17 +0100 Subject: [PATCH 11/15] bumped pp3 version number as requested by Floessie and Hombre57 --- rtgui/ppversion.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rtgui/ppversion.h b/rtgui/ppversion.h index c6f2598b0..1eb54d68b 100644 --- a/rtgui/ppversion.h +++ b/rtgui/ppversion.h @@ -1,13 +1,15 @@ #pragma once // This number has to be incremented whenever the PP3 file format is modified or the behaviour of a tool changes -#define PPVERSION 329 +#define PPVERSION 330 #define PPVERSION_AEXP 301 //value of PPVERSION when auto exposure algorithm was modified /* Log of version changes + 330 2018-20-01 + Added 'Auto-matched Tone Curve' button, performing histogram matching 329 2017-12-09 - Added 'Enabled' flag for Channel Mixer, RGB Curves and HSV Equalizer + Added 'Enabled' flag for Channel Mixer, RGB Curves, HSV Equalizer and L*a*b* Adjustments 328 2017-11-22 Fix wrong type of ff_clipControl 327 2017-09-15 From 88123cdd146bc73d314fb0312030af013217996f Mon Sep 17 00:00:00 2001 From: heckflosse Date: Sun, 21 Jan 2018 18:01:07 +0100 Subject: [PATCH 12/15] Set last curve point of auto matched curve always to 1;1 --- rtengine/histmatching.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc index 5b7647f49..be618d4d7 100644 --- a/rtengine/histmatching.cc +++ b/rtengine/histmatching.cc @@ -119,7 +119,7 @@ void mappingToCurve(const std::vector &mapping, std::vector &curve) doit(idx, int(mapping.size()), step, idx - step > step / 2); if (curve.size() <= 2 || curve.back() < 0.99 || (1 - curve[curve.size()-2] > step / 512.0 && curve.back() < coord(mapping.back()))) { curve.emplace_back(1.0); - curve.emplace_back(coord(mapping.back())); + curve.emplace_back(1.0); } if (curve.size() < 4) { curve = { DCT_Linear }; // not enough points, fall back to linear From 44984a911b397548e5f58927e0ac23cd5924a227 Mon Sep 17 00:00:00 2001 From: heckflosse Date: Sun, 21 Jan 2018 18:03:51 +0100 Subject: [PATCH 13/15] Don't set exposure to zero when 'auto levels' is disabled and 'auto matched tone curve' is enabled --- rtengine/improccoordinator.cc | 5 ++++- rtengine/simpleprocess.cc | 6 +++++- rtgui/tonecurve.cc | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index d390d94ca..d3a32140a 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -449,10 +449,13 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); + if (params.toneCurve.autoexp) { + params.toneCurve.expcomp = 0.0; + } + params.toneCurve.autoexp = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; - params.toneCurve.expcomp = 0.0; params.toneCurve.brightness = 0; params.toneCurve.contrast = 0; params.toneCurve.black = 0; diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 0cc223da1..45956f262 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -743,14 +743,18 @@ private: if (params.toneCurve.histmatching) { imgsrc->getAutoMatchedToneCurve(params.toneCurve.curve); + if (params.toneCurve.autoexp) { + params.toneCurve.expcomp = 0.0; + } + params.toneCurve.autoexp = false; params.toneCurve.curveMode = ToneCurveParams::TcMode::FILMLIKE; params.toneCurve.curve2 = { 0 }; - params.toneCurve.expcomp = 0.0; params.toneCurve.brightness = 0; params.toneCurve.contrast = 0; params.toneCurve.black = 0; params.toneCurve.hlcompr = 0; + } // at this stage, we can flush the raw data to free up quite an important amount of memory diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index 4a25794ff..fd4c0827d 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -936,7 +936,6 @@ bool ToneCurve::histmatchingComputed() GThreadLock lock; disableListener(); enableAll(); - expcomp->setValue(0); brightness->setValue(0); contrast->setValue(0); black->setValue(0); @@ -947,6 +946,7 @@ bool ToneCurve::histmatchingComputed() } if (autolevels->get_active() ) { + expcomp->setValue(0); autoconn.block(true); autolevels->set_active(false); autoconn.block(false); From d44a3b8bb3fba4226fd8581797b4775a1dfefdc8 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Sun, 21 Jan 2018 19:00:40 +0100 Subject: [PATCH 14/15] added tooltip to the hisogram matching button --- rtdata/languages/default | 1 + rtgui/tonecurve.cc | 2 ++ 2 files changed, 3 insertions(+) diff --git a/rtdata/languages/default b/rtdata/languages/default index acf03e48b..503600e28 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1554,6 +1554,7 @@ TP_EXPOSURE_CURVEEDITOR2;Tone curve 2 TP_EXPOSURE_CURVEEDITOR2_TOOLTIP;Please refer to the "Exposure > Tone Curves" RawPedia article to learn how to achieve the best results by using two tone curves. TP_EXPOSURE_EXPCOMP;Exposure compensation TP_EXPOSURE_HISTMATCHING;Auto-matched Tone Curve +TP_EXPOSURE_HISTMATCHING_TOOLTIP;Automatically adjust sliders and curves (except exposure compensation) to match the look of the embedded JPEG thumbnail. TP_EXPOSURE_LABEL;Exposure TP_EXPOSURE_SATURATION;Saturation TP_EXPOSURE_TCMODE_FILMLIKE;Film-like diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index fd4c0827d..ce938178e 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -127,6 +127,7 @@ ToneCurve::ToneCurve () : FoldableToolPanel(this, "tonecurve", M("TP_EXPOSURE_LA pack_start (*Gtk::manage (new Gtk::HSeparator())); histmatching = Gtk::manage(new Gtk::ToggleButton(M("TP_EXPOSURE_HISTMATCHING"))); + histmatching->set_tooltip_markup(M("TP_EXPOSURE_HISTMATCHING_TOOLTIP")); histmatchconn = histmatching->signal_toggled().connect(sigc::mem_fun(*this, &ToneCurve::histmatchingToggled)); pack_start(*histmatching, true, true, 2); @@ -448,6 +449,7 @@ void ToneCurve::setRaw (bool raw) disableListener (); method->set_sensitive (raw); hrenabled->set_sensitive (raw); + histmatching->set_sensitive(raw); enableListener (); } From b13db578eb2335432c1ef083eb76a55314c4e807 Mon Sep 17 00:00:00 2001 From: Alberto Griggio Date: Sun, 21 Jan 2018 19:21:41 +0100 Subject: [PATCH 15/15] do not reset highlight compression when doing histogram matching --- rtengine/improccoordinator.cc | 1 - rtengine/simpleprocess.cc | 1 - rtgui/tonecurve.cc | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index d3a32140a..62891a03f 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -459,7 +459,6 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) params.toneCurve.brightness = 0; params.toneCurve.contrast = 0; params.toneCurve.black = 0; - params.toneCurve.hlcompr = 0; if (aeListener) { aeListener->autoMatchedToneCurveChanged(params.toneCurve.curveMode, params.toneCurve.curve); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 45956f262..685e1d53e 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -753,7 +753,6 @@ private: params.toneCurve.brightness = 0; params.toneCurve.contrast = 0; params.toneCurve.black = 0; - params.toneCurve.hlcompr = 0; } diff --git a/rtgui/tonecurve.cc b/rtgui/tonecurve.cc index ce938178e..07822f857 100644 --- a/rtgui/tonecurve.cc +++ b/rtgui/tonecurve.cc @@ -567,7 +567,9 @@ void ToneCurve::adjusterChanged (Adjuster* a, double newval) return; } - setHistmatching(false); + if (a != expcomp && a != hlcompr && a != hlcomprthresh) { + setHistmatching(false); + } Glib::ustring costr;