Improve performance of histogram/waveform updates
Only perform calculations for the currently shown scope. Cache the waveform so it can be reused when the scope is resized. Increase speed of waveform rendering.
This commit is contained in:
@@ -1640,31 +1640,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange)
|
||||
}
|
||||
|
||||
if (hListener) {
|
||||
if (hListener->updateHistogram()) {
|
||||
updateLRGBHistograms();
|
||||
}
|
||||
if (hListener->updateWaveform()) {
|
||||
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()
|
||||
);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -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). */
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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,6 +912,7 @@ void HistogramArea::update(
|
||||
)
|
||||
{
|
||||
if (histRed) {
|
||||
if (scopeType == 0) {
|
||||
rhist = histRed;
|
||||
ghist = histGreen;
|
||||
bhist = histBlue;
|
||||
@@ -882,6 +921,7 @@ void HistogramArea::update(
|
||||
rhistRaw = histRedRaw;
|
||||
ghistRaw = histGreenRaw;
|
||||
bhistRaw = histBlueRaw;
|
||||
} else if (scopeType == 1) {
|
||||
waveform_scale = waveformScale;
|
||||
if (waveform_width != waveformWidth) {
|
||||
waveform_width = waveformWidth;
|
||||
@@ -895,6 +935,8 @@ void HistogramArea::update(
|
||||
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;
|
||||
}
|
||||
valid = true;
|
||||
} else {
|
||||
valid = false;
|
||||
@@ -1185,31 +1227,35 @@ void HistogramArea::drawWaveform(Cairo::RefPtr<Cairo::Context> &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<unsigned char[]> buffer(new unsigned char[256 * cairo_stride]);
|
||||
|
||||
if (wave_buffer_dirty) {
|
||||
wave_buffer.reset(new unsigned char[256 * cairo_stride]);
|
||||
|
||||
// Clear waveform.
|
||||
memset(buffer.get(), 0, 256 * cairo_stride);
|
||||
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 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);
|
||||
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) {
|
||||
buffer[val * cairo_stride + col * 4 + 3] = 0;
|
||||
*(uint32_t*)&(wave_buffer[(255 - val) * cairo_stride + col * 4]) = 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;
|
||||
// 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<Cairo::ImageSurface> 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<double>(w) / waveform_width, (h - 2 * padding) / 256.0);
|
||||
|
@@ -159,6 +159,8 @@ protected:
|
||||
int waveform_scale;
|
||||
int waveform_width;
|
||||
std::unique_ptr<int[][256]> rwave, gwave, bwave;
|
||||
std::unique_ptr<unsigned char[]> 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);
|
||||
};
|
||||
|
Reference in New Issue
Block a user