diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 37a81f3d1..4927c4e1f 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -1640,31 +1640,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } if (hListener) { - updateLRGBHistograms(); - updateWaveforms(); - hListener->histogramChanged( - histRed, - histGreen, - histBlue, - histLuma, - histToneCurve, - histLCurve, - histCCurve, - /*histCLurve, - * histLLCurve,*/ - histLCAM, - histCCAM, - histRedRaw, - histGreenRaw, - histBlueRaw, - histChroma, - histLRETI, - waveformScale, - waveformWidth, - waveformRed.get(), - waveformGreen.get(), - waveformBlue.get() - ); + if (hListener->updateHistogram()) { + updateLRGBHistograms(); + } + if (hListener->updateWaveform()) { + updateWaveforms(); + } + notifyHistogramChanged(); } } @@ -1765,6 +1747,33 @@ void ImProcCoordinator::setScale(int prevscale) } +void ImProcCoordinator::notifyHistogramChanged() +{ + if (hListener) { + hListener->histogramChanged( + histRed, + histGreen, + histBlue, + histLuma, + histToneCurve, + histLCurve, + histCCurve, + histLCAM, + histCCAM, + histRedRaw, + histGreenRaw, + histBlueRaw, + histChroma, + histLRETI, + waveformScale, + waveformWidth, + waveformRed.get(), + waveformGreen.get(), + waveformBlue.get() + ); + } +} + void ImProcCoordinator::updateLRGBHistograms() { @@ -2305,4 +2314,20 @@ void ImProcCoordinator::setHighQualComputed() highQualityComputed = true; } +void ImProcCoordinator::updateWaveform() +{ + if (hListener) { + updateWaveforms(); + notifyHistogramChanged(); + } +} + +void ImProcCoordinator::updateHistogram() +{ + if (hListener) { + updateLRGBHistograms(); + notifyHistogramChanged(); + } +} + } diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index 367f12a43..92b5e9346 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -54,7 +54,7 @@ class Crop; * but using this class' LUT and other precomputed parameters. The main preview area is displaying a non framed Crop object, * while detail windows are framed Crop objects. */ -class ImProcCoordinator final : public StagedImageProcessor +class ImProcCoordinator final : public StagedImageProcessor, public HistogramObservable { friend class Crop; @@ -199,6 +199,7 @@ protected: MyMutex minit; // to gain mutually exclusive access to ... to what exactly? + void notifyHistogramChanged(); void reallocAll(); void updateLRGBHistograms(); void updateWaveforms(); @@ -454,7 +455,11 @@ public: } void setHistogramListener (HistogramListener *h) override { + if (hListener) { + hListener->setObservable(nullptr); + } hListener = h; + h->setObservable(this); } void setAutoCamListener (AutoCamListener* acl) override { @@ -555,6 +560,8 @@ public: } denoiseInfoStore; + void updateHistogram() override; + void updateWaveform() override; }; } diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 30fa2ec67..4121b4f49 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -302,6 +302,8 @@ public: virtual void sizeChanged(int w, int h, int ow, int oh) = 0; }; +class HistogramObservable; + /** This listener is used when the histogram of the final image has changed. */ class HistogramListener { @@ -334,6 +336,21 @@ public: const int waveformGreen[][256], const int waveformBlue[][256] ) = 0; + /** Tells which observable is notifying the listener. */ + virtual void setObservable(HistogramObservable* observable) = 0; + /** Returns if the listener wants the histogram to be updated. */ + virtual bool updateHistogram(void) = 0; + /** Returns if the listener wants the waveform to be updated. */ + virtual bool updateWaveform(void) = 0; +}; + +class HistogramObservable +{ +public: + /** Tells the observable to update the histogram data. */ + virtual void updateHistogram() = 0; + /** Tells the observable to update the waveform data. */ + virtual void updateWaveform() = 0; }; /** This listener is used when the auto exposure has been recomputed (e.g. when the clipping ratio changed). */ diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 28747e063..5bf3e54d8 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -470,7 +470,8 @@ EditorPanel::EditorPanel (FilePanel* filePanel) iBeforeLockON (nullptr), iBeforeLockOFF (nullptr), previewHandler (nullptr), beforePreviewHandler (nullptr), beforeIarea (nullptr), beforeBox (nullptr), afterBox (nullptr), beforeLabel (nullptr), afterLabel (nullptr), beforeHeaderBox (nullptr), afterHeaderBox (nullptr), parent (nullptr), parentWindow (nullptr), openThm (nullptr), - selectedFrame(0), isrc (nullptr), ipc (nullptr), beforeIpc (nullptr), err (0), isProcessing (false) + selectedFrame(0), isrc (nullptr), ipc (nullptr), beforeIpc (nullptr), err (0), isProcessing (false), + histogram_observable(nullptr), histogram_scope_type(HistogramPanelListener::NONE) { epih = new EditorPanelIdleHelper; @@ -2260,6 +2261,40 @@ void EditorPanel::histogramChanged( tpc->updateCurveBackgroundHistogram(histToneCurve, histLCurve, histCCurve, histLCAM, histCCAM, histRed, histGreen, histBlue, histLuma, histLRETI); } +void EditorPanel::setObservable(rtengine::HistogramObservable* observable) +{ + histogram_observable = observable; +} + +bool EditorPanel::updateHistogram(void) +{ + return histogram_scope_type == HistogramPanelListener::HISTOGRAM + || histogram_scope_type == HistogramPanelListener::NONE; +} + +bool EditorPanel::updateWaveform(void) +{ + return histogram_scope_type == HistogramPanelListener::WAVEFORM + || histogram_scope_type == HistogramPanelListener::NONE; +} + +void EditorPanel::scopeTypeChanged(ScopeType new_type) +{ + histogram_scope_type = new_type; + + if (!histogram_observable) { + return; + } + + // Make sure the new scope is updated since we only actively update the + // current scope. + if (new_type == HistogramPanelListener::HISTOGRAM) { + histogram_observable->updateHistogram(); + } else if (new_type == HistogramPanelListener::WAVEFORM) { + histogram_observable->updateWaveform(); + } +} + bool EditorPanel::CheckSidePanelsVisibility() { if (tbTopPanel_1) { @@ -2376,6 +2411,10 @@ void EditorPanel::updateHistogramPosition (int oldPosition, int newPosition) break; } + if (histogramPanel) { + histogramPanel->setPanelListener(this); + } + iareapanel->imageArea->setPointerMotionHListener (histogramPanel); } diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 6a1fb585f..1794de11b 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -55,6 +55,7 @@ class EditorPanel final : public ThumbnailListener, public HistoryBeforeLineListener, public rtengine::HistogramListener, + public HistogramPanelListener, public rtengine::NonCopyable { public: @@ -133,6 +134,12 @@ public: const int waveformGreen[][256], const int waveformBlue[][256] ) override; + void setObservable(rtengine::HistogramObservable* observable) override; + bool updateHistogram(void) override; + bool updateWaveform(void) override; + + // HistogramPanelListener + void scopeTypeChanged(ScopeType new_type) override; // event handlers void info_toggled (); @@ -265,4 +272,7 @@ private: bool isProcessing; IdleRegister idle_register; + + rtengine::HistogramObservable* histogram_observable; + ScopeType histogram_scope_type; }; diff --git a/rtgui/histogrampanel.cc b/rtgui/histogrampanel.cc index 8c2d971eb..d3d92880d 100644 --- a/rtgui/histogrampanel.cc +++ b/rtgui/histogrampanel.cc @@ -32,7 +32,7 @@ using namespace rtengine; // // // HistogramPanel -HistogramPanel::HistogramPanel () +HistogramPanel::HistogramPanel () : panel_listener(nullptr) { setExpandAlignProperties(this, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL); set_name("HistogramPanel"); @@ -355,12 +355,20 @@ void HistogramPanel::type_changed() showRAW->set_sensitive(); showMode->set_sensitive(); histogramRGBArea = histogramRGBAreaHori.get(); + if (panel_listener) { + updateHistAreaOptions(); + panel_listener->scopeTypeChanged(HistogramPanelListener::HISTOGRAM); + } } else { showValue->set_sensitive(false); showChro->set_sensitive(false); showRAW->set_sensitive(false); showMode->set_sensitive(false); histogramRGBArea = histogramRGBAreaVert.get(); + if (panel_listener) { + updateHistAreaOptions(); + panel_listener->scopeTypeChanged(HistogramPanelListener::WAVEFORM); + } } if (showBAR->get_active()) { @@ -383,7 +391,8 @@ void HistogramPanel::bar_toggled () void HistogramPanel::rgbv_toggled () { // Update Display - histogramArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), options.histogramDrawMode, options.histogramScopeType); + updateHistAreaOptions(); + histogramArea->updateBackBuffer (); histogramArea->queue_draw (); histogramRGBArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), showBAR->get_active()); @@ -441,6 +450,35 @@ void HistogramPanel::toggleButtonMode () showMode->set_image(*mode2Image); } +void HistogramPanel::setPanelListener(HistogramPanelListener* listener) +{ + panel_listener = listener; + + if (listener) { + HistogramPanelListener::ScopeType type; + if (options.histogramScopeType == 0) { + type = HistogramPanelListener::HISTOGRAM; + } else { + type = HistogramPanelListener::WAVEFORM; + } + listener->scopeTypeChanged(type); + } +} + +void HistogramPanel::updateHistAreaOptions() +{ + histogramArea->updateOptions( + showRed->get_active(), + showGreen->get_active(), + showBlue->get_active(), + showValue->get_active(), + showChro->get_active(), + showRAW->get_active(), + options.histogramDrawMode, + options.histogramScopeType + ); +} + // // // @@ -775,7 +813,7 @@ void HistogramRGBAreaVert::get_preferred_width_for_height_vfunc (int height, int // // HistogramArea HistogramArea::HistogramArea (DrawModeListener *fml) : - waveform_width(0), + waveform_width(0), wave_buffer_dirty(true), valid(false), drawMode(options.histogramDrawMode), myDrawModeListener(fml), scopeType(options.histogramScopeType), oldwidth(-1), oldheight(-1), @@ -854,7 +892,7 @@ void HistogramArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool options.histogramDrawMode = drawMode = mode; options.histogramScopeType = scopeType = type; - updateBackBuffer (); + wave_buffer_dirty = true; } void HistogramArea::update( @@ -874,27 +912,31 @@ void HistogramArea::update( ) { if (histRed) { - rhist = histRed; - ghist = histGreen; - bhist = histBlue; - lhist = histLuma; - chist = histChroma; - rhistRaw = histRedRaw; - ghistRaw = histGreenRaw; - bhistRaw = histBlueRaw; - waveform_scale = waveformScale; - if (waveform_width != waveformWidth) { - waveform_width = waveformWidth; - rwave.reset(new int[waveformWidth][256]); - gwave.reset(new int[waveformWidth][256]); - bwave.reset(new int[waveformWidth][256]); + if (scopeType == 0) { + rhist = histRed; + ghist = histGreen; + bhist = histBlue; + lhist = histLuma; + chist = histChroma; + rhistRaw = histRedRaw; + ghistRaw = histGreenRaw; + bhistRaw = histBlueRaw; + } else if (scopeType == 1) { + waveform_scale = waveformScale; + if (waveform_width != waveformWidth) { + waveform_width = waveformWidth; + rwave.reset(new int[waveformWidth][256]); + gwave.reset(new int[waveformWidth][256]); + bwave.reset(new int[waveformWidth][256]); + } + int (* const rw)[256] = rwave.get(); + int (* const gw)[256] = gwave.get(); + int (* const bw)[256] = bwave.get(); + memcpy(rw, waveformRed, 256 * waveformWidth * sizeof(rw[0][0])); + memcpy(gw, waveformGreen, 256 * waveformWidth * sizeof(gw[0][0])); + memcpy(bw, waveformBlue, 256 * waveformWidth * sizeof(bw[0][0])); + wave_buffer_dirty = true; } - int (* const rw)[256] = rwave.get(); - int (* const gw)[256] = gwave.get(); - int (* const bw)[256] = bwave.get(); - memcpy(rw, waveformRed, 256 * waveformWidth * sizeof(rw[0][0])); - memcpy(gw, waveformGreen, 256 * waveformWidth * sizeof(gw[0][0])); - memcpy(bw, waveformBlue, 256 * waveformWidth * sizeof(bw[0][0])); valid = true; } else { valid = false; @@ -1185,31 +1227,35 @@ void HistogramArea::drawWaveform(Cairo::RefPtr &cr, int w, int h // See Cairo documentation on stride. const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, waveform_width); - std::unique_ptr buffer(new unsigned char[256 * cairo_stride]); - // Clear waveform. - memset(buffer.get(), 0, 256 * cairo_stride); + if (wave_buffer_dirty) { + wave_buffer.reset(new unsigned char[256 * cairo_stride]); - // TODO: Optimize. - for (int col = 0; col < waveform_width; col++) { - for (int val = 0; val < 256; val++) { - const float r = needRed ? scale * rwave[col][255 - val] : 0.f; - const float g = needGreen ? scale * gwave[col][255 - val] : 0.f; - const float b = needBlue ? scale * bwave[col][255 - val] : 0.f; - const float value = (r > g && r > b) ? r : ((g > b) ? g : b); - if (value <= 0) { - buffer[val * cairo_stride + col * 4 + 3] = 0; - } else { - buffer[val * cairo_stride + col * 4 + 3] = value; - buffer[val * cairo_stride + col * 4 + 2] = r; - buffer[val * cairo_stride + col * 4 + 1] = g; - buffer[val * cairo_stride + col * 4] = b; + // Clear waveform. + memset(wave_buffer.get(), 0, 256 * cairo_stride); + + // TODO: Optimize. + for (int col = 0; col < waveform_width; col++) { + for (int val = 0; val < 256; val++) { + const unsigned char r = needRed ? scale * rwave[col][val] : 0; + const unsigned char g = needGreen ? scale * gwave[col][val] : 0; + const unsigned char b = needBlue ? scale * bwave[col][val] : 0; + const unsigned char value = (r > g && r > b) ? r : ((g > b) ? g : b); + if (value <= 0) { + *(uint32_t*)&(wave_buffer[(255 - val) * cairo_stride + col * 4]) = 0; + } else { + // Speedup with one memory access instead of four. + *(uint32_t*)&(wave_buffer[(255 - val) * cairo_stride + col * 4]) = + b | (g << 8) | (r << 16) | (value << 24); + } } } + + wave_buffer_dirty = false; } Cairo::RefPtr surface = Cairo::ImageSurface::create( - buffer.get(), Cairo::FORMAT_ARGB32, waveform_width, 256, cairo_stride); + wave_buffer.get(), Cairo::FORMAT_ARGB32, waveform_width, 256, cairo_stride); auto orig_matrix = cr->get_matrix(); cr->translate(0, padding); cr->scale(static_cast(w) / waveform_width, (h - 2 * padding) / 256.0); diff --git a/rtgui/histogrampanel.h b/rtgui/histogrampanel.h index f97d5188b..83099c68e 100644 --- a/rtgui/histogrampanel.h +++ b/rtgui/histogrampanel.h @@ -159,6 +159,8 @@ protected: int waveform_scale; int waveform_width; std::unique_ptr rwave, gwave, bwave; + std::unique_ptr wave_buffer; + bool wave_buffer_dirty; bool valid; int drawMode; @@ -214,6 +216,14 @@ private: void get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const override; }; +class HistogramPanelListener +{ +public: + enum ScopeType {HISTOGRAM, WAVEFORM, NONE}; + + virtual void scopeTypeChanged(ScopeType new_type) = 0; +}; + class HistogramPanel final : public Gtk::Grid, public PointerMotionListener, public DrawModeListener, public rtengine::NonCopyable { @@ -255,9 +265,12 @@ protected: Gtk::Image *mode1Image; Gtk::Image *mode2Image; + HistogramPanelListener* panel_listener; + sigc::connection rconn; void setHistInvalid (); void showRGBBar(); + void updateHistAreaOptions(); public: @@ -304,4 +317,6 @@ public: // drawModeListener interface void toggleButtonMode () override; + + void setPanelListener(HistogramPanelListener* listener); };