diff --git a/rtdata/languages/default b/rtdata/languages/default index 4fff6c412..3621596f3 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1157,6 +1157,10 @@ PREFERENCES_TAB_IMPROC;Image Processing PREFERENCES_TAB_PERFORMANCE;Performance & Quality PREFERENCES_TAB_SOUND;Sounds PREFERENCES_THEME;Theme +PREFERENCES_THUMBNAIL_INSPECTOR_JPEG;Embedded JPEG preview +PREFERENCES_THUMBNAIL_INSPECTOR_MODE;Image to show +PREFERENCES_THUMBNAIL_INSPECTOR_RAW;Neutral raw rendering +PREFERENCES_THUMBNAIL_INSPECTOR_RAW_IF_NO_JPEG_FULLSIZE;Embedded JPEG if fullsize, neutral raw otherwise PREFERENCES_TIMAX;High PREFERENCES_TINB;Number of tiles PREFERENCES_TISTD;Standard diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 7fe347797..077974244 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -1518,7 +1518,7 @@ void RawImageSource::vflip (Imagefloat* image) //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -int RawImageSource::load (const Glib::ustring &fname) +int RawImageSource::load (const Glib::ustring &fname, bool firstFrameOnly) { MyTime t1, t2; @@ -1535,7 +1535,7 @@ int RawImageSource::load (const Glib::ustring &fname) if (errCode) { return errCode; } - numFrames = ri->getFrameCount(); + numFrames = firstFrameOnly ? 1 : ri->getFrameCount(); errCode = 0; diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index 884c32dbe..04b7396bc 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -118,7 +118,8 @@ public: RawImageSource (); ~RawImageSource (); - int load (const Glib::ustring &fname); + int load(const Glib::ustring &fname) { return load(fname, false); } + int load(const Glib::ustring &fname, bool firstFrameOnly); void preprocess (const RAWParams &raw, const LensProfParams &lensProf, const CoarseTransformParams& coarse, bool prepareDenoise = true); void demosaic (const RAWParams &raw); void retinex (const ColorManagementParams& cmp, const RetinexParams &deh, const 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); diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 05d4bead8..8c6b7155c 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -36,7 +36,10 @@ #include "jpeg.h" #include "../rtgui/ppversion.h" #include "improccoordinator.h" +#include "settings.h" #include +#include "StopWatch.h" +#include "median.h" namespace { @@ -147,6 +150,8 @@ extern Options options; namespace rtengine { +extern const Settings *settings; + using namespace procparams; Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, int fixwh, double wbEq, bool inspectorMode) @@ -261,13 +266,92 @@ Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h, return tpp; } + +namespace { + +Image8 *load_inspector_mode(const Glib::ustring &fname, RawMetaDataLocation &rml, eSensorType &sensorType, int &w, int &h) +{ + BENCHFUN + + RawImageSource src; + int err = src.load(fname, true); + if (err) { + return nullptr; + } + + src.getFullSize(w, h); + sensorType = src.getSensorType(); + + ProcParams neutral; + neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); + neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); + neutral.icm.input = "(camera)"; + neutral.icm.working = "RT_sRGB"; + + src.preprocess(neutral.raw, neutral.lensProf, neutral.coarse, false); + src.demosaic(neutral.raw); + + PreviewProps pp(0, 0, w, h, 1); + + Imagefloat tmp(w, h); + src.getImage(src.getWB(), TR_NONE, &tmp, pp, neutral.toneCurve, neutral.raw); + src.convertColorSpace(&tmp, neutral.icm, src.getWB()); + + Image8 *img = new Image8(w, h); + const float f = 255.f/65535.f; +#ifdef _OPENMP + #pragma omp parallel for +#endif + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + float r = tmp.r(y, x); + float g = tmp.g(y, x); + float b = tmp.b(y, x); + // avoid magenta highlights + if (r > MAXVALF && b > MAXVALF) { + float v = CLIP((r + g + b) / 3.f) * f; + img->r(y, x) = img->g(y, x) = img->b(y, x) = v; + } else { + img->r(y, x) = Color::gamma_srgbclipped(r) * f; + img->g(y, x) = Color::gamma_srgbclipped(g) * f; + img->b(y, x) = Color::gamma_srgbclipped(b) * f; + } + } + } + + return img; +} + +} // namespace + Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, RawMetaDataLocation& rml, eSensorType &sensorType, int &w, int &h, int fixwh, bool rotate, bool inspectorMode) { + Thumbnail* tpp = new Thumbnail (); + tpp->isRaw = 1; + memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix)); + tpp->colorMatrix[0][0] = 1.0; + tpp->colorMatrix[1][1] = 1.0; + tpp->colorMatrix[2][2] = 1.0; + + if (inspectorMode && !forHistogramMatching && settings->thumbnail_inspector_mode == Settings::ThumbnailInspectorMode::RAW) { + Image8 *img = load_inspector_mode(fname, rml, sensorType, w, h); + if (!img) { + delete tpp; + return nullptr; + } + + tpp->scale = 1.; + tpp->thumbImg = img; + + return tpp; + } + RawImage *ri = new RawImage (fname); unsigned int imageNum = 0; int r = ri->loadRaw (false, imageNum, false); if ( r ) { + delete tpp; delete ri; sensorType = ST_NONE; return nullptr; @@ -301,24 +385,33 @@ Thumbnail* Thumbnail::loadQuickFromRaw (const Glib::ustring& fname, RawMetaDataL // did we succeed? if ( err ) { printf ("Could not extract thumb from %s\n", fname.data()); + delete tpp; delete img; delete ri; return nullptr; } - Thumbnail* tpp = new Thumbnail (); - - tpp->isRaw = 1; - memset (tpp->colorMatrix, 0, sizeof (tpp->colorMatrix)); - tpp->colorMatrix[0][0] = 1.0; - tpp->colorMatrix[1][1] = 1.0; - tpp->colorMatrix[2][2] = 1.0; - if (inspectorMode) { // Special case, meaning that we want a full sized thumbnail image (e.g. for the Inspector feature) w = img->getWidth(); h = img->getHeight(); tpp->scale = 1.; + + if (!forHistogramMatching && settings->thumbnail_inspector_mode == Settings::ThumbnailInspectorMode::RAW_IF_NOT_JPEG_FULLSIZE && float(std::max(w, h))/float(std::max(ri->get_width(), ri->get_height())) < 0.9f) { + delete img; + delete ri; + + img = load_inspector_mode(fname, rml, sensorType, w, h); + if (!img) { + delete tpp; + return nullptr; + } + + tpp->scale = 1.; + tpp->thumbImg = img; + + return tpp; + } } else { if (fixwh == 1) { w = h * img->getWidth() / img->getHeight(); diff --git a/rtengine/settings.h b/rtengine/settings.h index eebc2fb4b..74a855c3f 100644 --- a/rtengine/settings.h +++ b/rtengine/settings.h @@ -83,6 +83,13 @@ public: double level0_cbdl; double level123_cbdl; Glib::ustring lensfunDbDirectory; ///< The directory containing the lensfun database. If empty, the system defaults will be used (as described in http://lensfun.sourceforge.net/manual/dbsearch.html) + + enum class ThumbnailInspectorMode { + JPEG, + RAW, + RAW_IF_NOT_JPEG_FULLSIZE + }; + ThumbnailInspectorMode thumbnail_inspector_mode; /** Creates a new instance of Settings. * @return a pointer to the new Settings instance. */ diff --git a/rtgui/inspector.cc b/rtgui/inspector.cc index 4084e300f..6da62cd3b 100644 --- a/rtgui/inspector.cc +++ b/rtgui/inspector.cc @@ -210,6 +210,23 @@ void Inspector::switchImage (const Glib::ustring &fullPath) return; } + if (delayconn.connected()) { + delayconn.disconnect(); + } + + next_image_path = fullPath; + if (!options.inspectorDelay) { + doSwitchImage(); + } else { + delayconn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Inspector::doSwitchImage), options.inspectorDelay); + } +} + + +bool Inspector::doSwitchImage() +{ + Glib::ustring fullPath = next_image_path; + // we first check the size of the list, it may have been changed in Preference if (images.size() > size_t(options.maxInspectorBuffers)) { // deleting the last entries @@ -225,7 +242,6 @@ void Inspector::switchImage (const Glib::ustring &fullPath) if (fullPath.empty()) { currImage = nullptr; queue_draw(); - return; } else { bool found = false; @@ -264,6 +280,8 @@ void Inspector::switchImage (const Glib::ustring &fullPath) } } } + + return true; } void Inspector::deleteBuffers () diff --git a/rtgui/inspector.h b/rtgui/inspector.h index f68912dc1..ac8f52ee2 100644 --- a/rtgui/inspector.h +++ b/rtgui/inspector.h @@ -48,9 +48,14 @@ private: double zoom; bool active; + sigc::connection delayconn; + Glib::ustring next_image_path; + bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr); void deleteBuffers(); + bool doSwitchImage(); + public: Inspector(); ~Inspector(); diff --git a/rtgui/options.cc b/rtgui/options.cc index 485f9cc3b..2e8d20f96 100644 --- a/rtgui/options.cc +++ b/rtgui/options.cc @@ -426,6 +426,7 @@ void Options::setDefaults () #endif filledProfile = false; maxInspectorBuffers = 2; // a rather conservative value for low specced systems... + inspectorDelay = 0; serializeTiffRead = true; FileBrowserToolbarSingleRow = false; @@ -592,6 +593,8 @@ void Options::setDefaults () rtSettings.lensfunDbDirectory = ""; // set also in main.cc and main-cli.cc cropGuides = CROP_GUIDE_FULL; cropAutoFit = false; + + rtSettings.thumbnail_inspector_mode = rtengine::Settings::ThumbnailInspectorMode::JPEG; } Options* Options::copyFrom (Options* other) @@ -1054,6 +1057,10 @@ void Options::readFromFile (Glib::ustring fname) maxInspectorBuffers = keyFile.get_integer ("Performance", "MaxInspectorBuffers"); } + if (keyFile.has_key ("Performance", "InspectorDelay")) { + inspectorDelay = keyFile.get_integer("Performance", "InspectorDelay"); + } + if (keyFile.has_key ("Performance", "PreviewDemosaicFromSidecar")) { prevdemo = (prevdemo_t)keyFile.get_integer ("Performance", "PreviewDemosaicFromSidecar"); } @@ -1065,6 +1072,10 @@ void Options::readFromFile (Glib::ustring fname) if (keyFile.has_key ("Performance", "SerializeTiffRead")) { serializeTiffRead = keyFile.get_boolean ("Performance", "SerializeTiffRead"); } + + if (keyFile.has_key("Performance", "ThumbnailInspectorMode")) { + rtSettings.thumbnail_inspector_mode = static_cast(keyFile.get_integer("Performance", "ThumbnailInspectorMode")); + } } if (keyFile.has_group ("GUI")) { @@ -1841,9 +1852,11 @@ void Options::saveToFile (Glib::ustring fname) keyFile.set_integer ("Performance", "SIMPLNRAUT", rtSettings.leveldnautsimpl); keyFile.set_integer ("Performance", "ClutCacheSize", clutCacheSize); keyFile.set_integer ("Performance", "MaxInspectorBuffers", maxInspectorBuffers); + keyFile.set_integer ("Performance", "InspectorDelay", inspectorDelay); keyFile.set_integer ("Performance", "PreviewDemosaicFromSidecar", prevdemo); keyFile.set_boolean ("Performance", "Daubechies", rtSettings.daubech); keyFile.set_boolean ("Performance", "SerializeTiffRead", serializeTiffRead); + keyFile.set_integer("Performance", "ThumbnailInspectorMode", int(rtSettings.thumbnail_inspector_mode)); keyFile.set_string ("Output", "Format", saveFormat.format); keyFile.set_integer ("Output", "JpegQuality", saveFormat.jpegQuality); diff --git a/rtgui/options.h b/rtgui/options.h index a5eef1543..920968956 100644 --- a/rtgui/options.h +++ b/rtgui/options.h @@ -275,6 +275,7 @@ public: Glib::ustring clutsDir; int rgbDenoiseThreadLimit; // maximum number of threads for the denoising tool ; 0 = use the maximum available int maxInspectorBuffers; // maximum number of buffers (i.e. images) for the Inspector feature + int inspectorDelay; int clutCacheSize; bool filledProfile; // Used as reminder for the ProfilePanel "mode" prevdemo_t prevdemo; // Demosaicing method used for the <100% preview diff --git a/rtgui/preferences.cc b/rtgui/preferences.cc index e8a5f7b49..108991886 100644 --- a/rtgui/preferences.cc +++ b/rtgui/preferences.cc @@ -657,7 +657,19 @@ Gtk::Widget* Preferences::getPerformancePanel () maxInspectorBuffersSB->set_range (1, 12); // ... we have to set a limit, 12 seem to be enough even for systems with tons of RAM maxIBuffersHB->pack_start (*maxIBufferLbl, Gtk::PACK_SHRINK, 0); maxIBuffersHB->pack_end (*maxInspectorBuffersSB, Gtk::PACK_SHRINK, 0); - finspect->add (*maxIBuffersHB); + + Gtk::VBox *inspectorvb = Gtk::manage(new Gtk::VBox()); + inspectorvb->add(*maxIBuffersHB); + + Gtk::HBox *insphb = Gtk::manage(new Gtk::HBox()); + thumbnailInspectorMode = Gtk::manage(new Gtk::ComboBoxText()); + thumbnailInspectorMode->append(M("PREFERENCES_THUMBNAIL_INSPECTOR_JPEG")); + thumbnailInspectorMode->append(M("PREFERENCES_THUMBNAIL_INSPECTOR_RAW")); + thumbnailInspectorMode->append(M("PREFERENCES_THUMBNAIL_INSPECTOR_RAW_IF_NO_JPEG_FULLSIZE")); + insphb->pack_start(*Gtk::manage(new Gtk::Label(M("PREFERENCES_THUMBNAIL_INSPECTOR_MODE") + ": ")), Gtk::PACK_SHRINK, 4); + insphb->pack_start(*thumbnailInspectorMode); + inspectorvb->pack_start(*insphb); + finspect->add (*inspectorvb); mainContainer->pack_start (*finspect, Gtk::PACK_SHRINK, 4); Gtk::Frame* fdenoise = Gtk::manage ( new Gtk::Frame (M ("PREFERENCES_NOISE")) ); @@ -1853,6 +1865,7 @@ void Preferences::storePreferences () moptions.rgbDenoiseThreadLimit = rgbDenoiseTreadLimitSB->get_value_as_int(); moptions.clutCacheSize = clutCacheSizeSB->get_value_as_int(); moptions.maxInspectorBuffers = maxInspectorBuffersSB->get_value_as_int(); + moptions.rtSettings.thumbnail_inspector_mode = static_cast(thumbnailInspectorMode->get_active_row_number()); // Sounds only on Windows and Linux #if defined(WIN32) || defined(__linux__) @@ -2072,6 +2085,7 @@ void Preferences::fillPreferences () rgbDenoiseTreadLimitSB->set_value (moptions.rgbDenoiseThreadLimit); clutCacheSizeSB->set_value (moptions.clutCacheSize); maxInspectorBuffersSB->set_value (moptions.maxInspectorBuffers); + thumbnailInspectorMode->set_active(int(moptions.rtSettings.thumbnail_inspector_mode)); darkFrameDir->set_current_folder ( moptions.rtSettings.darkFramesPath ); darkFrameChanged (); diff --git a/rtgui/preferences.h b/rtgui/preferences.h index cb0e6c709..ecf3638d9 100644 --- a/rtgui/preferences.h +++ b/rtgui/preferences.h @@ -166,6 +166,7 @@ class Preferences : public Gtk::Dialog, public ProfileStoreListener Gtk::SpinButton* rgbDenoiseTreadLimitSB; Gtk::SpinButton* clutCacheSizeSB; Gtk::SpinButton* maxInspectorBuffersSB; + Gtk::ComboBoxText *thumbnailInspectorMode; Gtk::CheckButton* ckbmenuGroupRank; Gtk::CheckButton* ckbmenuGroupLabel;