1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5  *
6  *  RawTherapee is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  RawTherapee is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 #include "histogrampanel.h"
20 #include "multilangmgr.h"
21 #include "guiutils.h"
22 #include "options.h"
23 #include <cstring>
24 #include <cmath>
25 #include "../rtengine/array2D.h"
26 #include "../rtengine/LUT.h"
27 #include "rtimage.h"
28 #include "../rtengine/color.h"
29 
30 using namespace rtengine;
31 
32 constexpr float HistogramArea::MAX_BRIGHT;
33 constexpr float HistogramArea::MIN_BRIGHT;
34 
35 using ScopeType = Options::ScopeType;
36 
37 namespace {
38 
39 constexpr float rgb_R[3] = { 1.f, 0.f, 0.f };
40 constexpr float rgb_G[3] = { 0.f, 1.f, 0.f };
41 constexpr float rgb_B[3] = { 0.f, 0.25f, 1.f };
42 
set_arr(array2D<int> & dst,const array2D<int> & src)43 void set_arr(array2D<int> &dst, const array2D<int> &src)
44 {
45     dst(src.width(), src.height());
46     for (int y = 0; y < src.height(); ++y) {
47         for (int x = 0; x < src.width(); ++x) {
48             dst[y][x] = src[y][x];
49         }
50     }
51 }
52 
53 constexpr double padding = 0;
54 
55 } // namespace
56 
57 //
58 //
59 // HistogramPanel
HistogramPanel()60 HistogramPanel::HistogramPanel () :
61     panel_listener(nullptr)
62 {
63     setExpandAlignProperties(this, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
64     set_name("HistogramPanel");
65 
66     histogramArea = Gtk::manage (new HistogramArea (this));
67     setExpandAlignProperties(histogramArea, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
68 
69     histogramRGBAreaHori.reset(new HistogramRGBAreaHori());
70     setExpandAlignProperties(histogramRGBAreaHori.get(), true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_END);
71 
72     histogramRGBAreaVert.reset(new HistogramRGBAreaVert());
73     setExpandAlignProperties(histogramRGBAreaVert.get(), false, true, Gtk::ALIGN_END, Gtk::ALIGN_FILL);
74 
75     switch (options.histogramScopeType) {
76         case ScopeType::NONE:
77         case ScopeType::HISTOGRAM_RAW:
78         case ScopeType::VECTORSCOPE_HC:
79         case ScopeType::VECTORSCOPE_HS:
80             histogramRGBArea = nullptr;
81             break;
82         case ScopeType::PARADE:
83         case ScopeType::WAVEFORM:
84             histogramRGBArea = histogramRGBAreaVert.get();
85             break;
86         case ScopeType::HISTOGRAM:
87             histogramRGBArea = histogramRGBAreaHori.get();
88             break;
89     }
90 
91     // connecting the two childs
92     histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaHori, &HistogramRGBArea::factorChanged) );
93     histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaVert, &HistogramRGBArea::factorChanged) );
94 
95     gfxGrid = Gtk::manage (new Gtk::Grid ());
96     gfxGrid->set_row_spacing(1);
97     gfxGrid->set_column_spacing(1);
98     gfxGrid->add(*histogramArea);
99     gfxGrid->attach_next_to(
100         *histogramRGBAreaVert, *histogramArea,
101         options.histogramPosition == 1 ? Gtk::POS_RIGHT : Gtk::POS_LEFT,
102         1,
103         1
104     );
105     gfxGrid->attach_next_to(*histogramRGBAreaHori, *histogramArea, Gtk::POS_BOTTOM, 1, 1);
106     histogramRGBAreaHori->set_no_show_all();
107     histogramRGBAreaVert->set_no_show_all();
108 
109     redImage   = new RTImage ("histogram-red-on-small.png");
110     greenImage = new RTImage ("histogram-green-on-small.png");
111     blueImage  = new RTImage ("histogram-blue-on-small.png");
112     valueImage = new RTImage ("histogram-silver-on-small.png");
113     chroImage  = new RTImage ("histogram-gold-on-small.png");
114     barImage   = new RTImage ("histogram-bar-on-small.png");
115 
116     redImage_g   = new RTImage ("histogram-red-off-small.png");
117     greenImage_g = new RTImage ("histogram-green-off-small.png");
118     blueImage_g  = new RTImage ("histogram-blue-off-small.png");
119     valueImage_g = new RTImage ("histogram-silver-off-small.png");
120     chroImage_g  = new RTImage ("histogram-gold-off-small.png");
121     barImage_g   = new RTImage ("histogram-bar-off-small.png");
122 
123     mode_images_[0] = new RTImage ("histogram-mode-linear-small.png");
124     mode_images_[1] = new RTImage ("histogram-mode-logx-small.png");
125     mode_images_[2] = new RTImage ("histogram-mode-logxy-small.png");
126     mode_tips_[0] = M("HISTOGRAM_TOOLTIP_MODE_LINEAR");
127     mode_tips_[1] = M("HISTOGRAM_TOOLTIP_MODE_LOG_X");
128     mode_tips_[2] = M("HISTOGRAM_TOOLTIP_MODE_LOG_XY");
129 
130     Gtk::Image* histImage = Gtk::manage(new RTImage("histogram-type-histogram-small.png"));
131     Gtk::Image* histRawImage = Gtk::manage(new RTImage("histogram-type-histogram-raw-small.png"));
132     Gtk::Image* paradeImage = Gtk::manage(new RTImage("histogram-type-parade-small.png"));
133     Gtk::Image* waveImage = Gtk::manage(new RTImage("histogram-type-waveform-small.png"));
134     Gtk::Image* vectHcImage = Gtk::manage(new RTImage("histogram-type-vectorscope-hc-small.png"));
135     Gtk::Image* vectHsImage = Gtk::manage(new RTImage("histogram-type-vectorscope-hs-small.png"));
136 
137     showRed   = Gtk::manage (new Gtk::ToggleButton ());
138     showGreen = Gtk::manage (new Gtk::ToggleButton ());
139     showBlue  = Gtk::manage (new Gtk::ToggleButton ());
140     showValue = Gtk::manage (new Gtk::ToggleButton ());
141     showChro  = Gtk::manage (new Gtk::ToggleButton ());
142     showMode  = Gtk::manage (new Gtk::Button ());
143     showBAR   = Gtk::manage (new Gtk::ToggleButton ());
144     scopeOptions = Gtk::manage (new Gtk::ToggleButton ());
145 
146     Gtk::RadioButtonGroup scopeTypeGroup;
147     scopeHistBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
148     scopeHistRawBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
149     scopeParadeBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
150     scopeWaveBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
151     scopeVectHcBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
152     scopeVectHsBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
153     scopeHistBtn->set_mode(false);
154     scopeHistRawBtn->set_mode(false);
155     scopeParadeBtn->set_mode(false);
156     scopeWaveBtn->set_mode(false);
157     scopeVectHcBtn->set_mode(false);
158     scopeVectHsBtn->set_mode(false);
159 
160     showRed->set_name("histButton");
161     showRed->set_can_focus(false);
162     showGreen->set_name("histButton");
163     showGreen->set_can_focus(false);
164     showBlue->set_name("histButton");
165     showBlue->set_can_focus(false);
166     showValue->set_name("histButton");
167     showValue->set_can_focus(false);
168     showChro->set_name("histButton");
169     showChro->set_can_focus(false);
170     showMode->set_name("histButton");
171     showMode->set_can_focus(false);
172     scopeOptions->set_name("histButton");
173     scopeOptions->set_can_focus(false);
174     showBAR->set_name("histButton");
175     showBAR->set_can_focus(false);
176     scopeHistBtn->set_name("histButton");
177     scopeHistBtn->set_can_focus(false);
178     scopeHistRawBtn->set_name("histButton");
179     scopeHistRawBtn->set_can_focus(false);
180     scopeParadeBtn->set_name("histButton");
181     scopeParadeBtn->set_can_focus(false);
182     scopeWaveBtn->set_name("histButton");
183     scopeWaveBtn->set_can_focus(false);
184     scopeVectHcBtn->set_name("histButton");
185     scopeVectHcBtn->set_can_focus(false);
186     scopeVectHsBtn->set_name("histButton");
187     scopeVectHsBtn->set_can_focus(false);
188 
189     showRed->set_relief (Gtk::RELIEF_NONE);
190     showGreen->set_relief (Gtk::RELIEF_NONE);
191     showBlue->set_relief (Gtk::RELIEF_NONE);
192     showValue->set_relief (Gtk::RELIEF_NONE);
193     showChro->set_relief (Gtk::RELIEF_NONE);
194     showMode->set_relief (Gtk::RELIEF_NONE);
195     scopeOptions->set_relief (Gtk::RELIEF_NONE);
196     showBAR->set_relief (Gtk::RELIEF_NONE);
197     scopeHistBtn->set_relief (Gtk::RELIEF_NONE);
198     scopeHistRawBtn->set_relief (Gtk::RELIEF_NONE);
199     scopeParadeBtn->set_relief (Gtk::RELIEF_NONE);
200     scopeWaveBtn->set_relief (Gtk::RELIEF_NONE);
201     scopeVectHcBtn->set_relief (Gtk::RELIEF_NONE);
202     scopeVectHsBtn->set_relief (Gtk::RELIEF_NONE);
203 
204     showRed->set_tooltip_text   (M("HISTOGRAM_TOOLTIP_R"));
205     showGreen->set_tooltip_text (M("HISTOGRAM_TOOLTIP_G"));
206     showBlue->set_tooltip_text  (M("HISTOGRAM_TOOLTIP_B"));
207     showValue->set_tooltip_text (M("HISTOGRAM_TOOLTIP_L"));
208     showChro->set_tooltip_text  (M("HISTOGRAM_TOOLTIP_CHRO"));
209     showMode->set_tooltip_text  (M("HISTOGRAM_TOOLTIP_MODE"));
210     scopeOptions->set_tooltip_text(M("HISTOGRAM_TOOLTIP_SHOW_OPTIONS"));
211     scopeHistBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM"));
212     scopeHistRawBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM_RAW"));
213     scopeParadeBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_PARADE"));
214     scopeWaveBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_WAVEFORM"));
215     scopeVectHcBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HC"));
216     scopeVectHsBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HS"));
217 
218     buttonGrid = Gtk::manage (new Gtk::Grid ());
219     buttonGrid->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
220     persistentButtons = Gtk::manage(new Gtk::Box());
221     persistentButtons->set_orientation(Gtk::ORIENTATION_VERTICAL);
222     optionButtons = Gtk::manage(new Gtk::Box());
223     optionButtons->set_orientation(Gtk::ORIENTATION_VERTICAL);
224 
225     showRed->set_active   (options.histogramRed);
226     showGreen->set_active (options.histogramGreen);
227     showBlue->set_active  (options.histogramBlue);
228     showValue->set_active (options.histogramLuma);
229     showChro->set_active  (options.histogramChroma);
230     // no showMode->set_active(), as it's not a ToggleButton
231     scopeOptions->set_active(options.histogramShowOptionButtons);
232     showBAR->set_active   (options.histogramBar);
233 
234     showRed->set_image   (showRed->get_active()   ? *redImage   : *redImage_g);
235     showGreen->set_image (showGreen->get_active() ? *greenImage : *greenImage_g);
236     showBlue->set_image  (showBlue->get_active()  ? *blueImage  : *blueImage_g);
237     showValue->set_image (showValue->get_active() ? *valueImage : *valueImage_g);
238     showChro->set_image  (showChro->get_active()  ? *chroImage  : *chroImage_g);
239     toggleButtonMode();
240     scopeHistBtn->set_image(*histImage);
241     scopeHistRawBtn->set_image(*histRawImage);
242     scopeParadeBtn->set_image(*paradeImage);
243     scopeWaveBtn->set_image(*waveImage);
244     scopeVectHcBtn->set_image(*vectHcImage);
245     scopeVectHsBtn->set_image(*vectHsImage);
246     switch(options.histogramScopeType) {
247         case ScopeType::HISTOGRAM:
248             scopeHistBtn->set_active();
249             break;
250         case ScopeType::HISTOGRAM_RAW:
251             scopeHistRawBtn->set_active();
252             break;
253         case ScopeType::PARADE:
254             scopeParadeBtn->set_active();
255             break;
256         case ScopeType::WAVEFORM:
257             scopeWaveBtn->set_active();
258             break;
259         case ScopeType::VECTORSCOPE_HS:
260             scopeVectHsBtn->set_active();
261             break;
262         case ScopeType::VECTORSCOPE_HC:
263             scopeVectHcBtn->set_active();
264             break;
265         case ScopeType::NONE:
266             break;
267     }
268     scopeOptions->set_image(*Gtk::manage(new RTImage("histogram-ellipsis-small.png")));
269     showBAR->set_image   (showBAR->get_active()   ? *barImage   : *barImage_g);
270 
271     setExpandAlignProperties(showRed  , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
272     setExpandAlignProperties(showGreen, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
273     setExpandAlignProperties(showBlue , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
274     setExpandAlignProperties(showValue, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
275     setExpandAlignProperties(showChro , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
276     setExpandAlignProperties(showMode , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
277     setExpandAlignProperties(scopeOptions, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
278     setExpandAlignProperties(showBAR  , false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
279     setExpandAlignProperties(scopeOptions, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
280     setExpandAlignProperties(scopeHistBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
281     setExpandAlignProperties(scopeHistRawBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
282     setExpandAlignProperties(scopeParadeBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
283     setExpandAlignProperties(scopeWaveBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
284     setExpandAlignProperties(scopeVectHcBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
285     setExpandAlignProperties(scopeVectHsBtn, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
286     setExpandAlignProperties(persistentButtons, false, true, Gtk::ALIGN_START, Gtk::ALIGN_FILL);
287     setExpandAlignProperties(optionButtons, false, true, Gtk::ALIGN_START, Gtk::ALIGN_FILL);
288 
289     showRed->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::red_toggled), showRed );
290     showGreen->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::green_toggled), showGreen );
291     showBlue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::blue_toggled), showBlue );
292     showValue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::value_toggled), showValue );
293     showChro->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::chro_toggled), showChro );
294     showMode->signal_released().connect( sigc::mem_fun(*this, &HistogramPanel::mode_released), showMode );
295     scopeOptions->signal_toggled().connect(sigc::mem_fun(*this, &HistogramPanel::scopeOptionsToggled));
296     showBAR->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::bar_toggled), showBAR );
297     scopeHistBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistBtn));
298     scopeHistRawBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistRawBtn));
299     scopeParadeBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeParadeBtn));
300     scopeWaveBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeWaveBtn));
301     scopeVectHcBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHcBtn));
302     scopeVectHsBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHsBtn));
303 
304     brightnessWidget = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_VERTICAL));
305     brightnessWidget->set_inverted();
306     brightnessWidget->set_range(log(HistogramArea::MIN_BRIGHT), log(HistogramArea::MAX_BRIGHT));
307     brightnessWidget->set_draw_value(false);
308     brightnessWidget->signal_value_changed().connect(sigc::mem_fun(*this, &HistogramPanel::brightnessWidgetValueChanged));
309     brightnessWidget->set_name("histScale");
310     brightnessWidget->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TRACE_BRIGHTNESS"));
311     setExpandAlignProperties(brightnessWidget, true, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_START);
312 
313     optionButtons->add(*showRed);
314     optionButtons->add(*showGreen);
315     optionButtons->add(*showBlue);
316     optionButtons->add(*showValue);
317     optionButtons->add(*showChro);
318     optionButtons->add(*showMode);
319     optionButtons->add(*showBAR);
320     optionButtons->add(*brightnessWidget);
321 
322     Gtk::VSeparator* separator = Gtk::manage(new Gtk::VSeparator());
323     setExpandAlignProperties(separator, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER);
324     persistentButtons->add(*scopeHistBtn);
325     persistentButtons->add(*scopeHistRawBtn);
326     persistentButtons->add(*scopeParadeBtn);
327     persistentButtons->add(*scopeWaveBtn);
328     persistentButtons->add(*scopeVectHsBtn);
329     persistentButtons->add(*scopeVectHcBtn);
330     persistentButtons->add(*separator);
331     persistentButtons->add(*scopeOptions);
332 
333     // Put the button vbox next to the window's border to be less disturbing
334     if (options.histogramPosition == 1) {
335         buttonGrid->add(*persistentButtons);
336         buttonGrid->add(*optionButtons);
337 
338         add (*buttonGrid);
339         add (*gfxGrid);
340     } else {
341         buttonGrid->add(*optionButtons);
342         buttonGrid->add(*persistentButtons);
343 
344         add (*gfxGrid);
345         add (*buttonGrid);
346     }
347 
348     show_all ();
349     optionButtons->set_no_show_all();
350     optionButtons->set_visible(options.histogramShowOptionButtons);
351 
352     type_changed();
353     updateHistAreaOptions();
354     if (histogramRGBArea) {
355         updateHistRGBAreaOptions();
356     }
357 
358     brightness_changed_connection = histogramArea->getBrighnessChangedSignal().connect(sigc::mem_fun(*this, &HistogramPanel::brightnessUpdated));
359     rconn = signal_size_allocate().connect( sigc::mem_fun(*this, &HistogramPanel::resized) );
360 
361     histogramArea->setBrightness(options.histogramTraceBrightness);
362 }
363 
~HistogramPanel()364 HistogramPanel::~HistogramPanel ()
365 {
366     delete redImage;
367     delete greenImage;
368     delete blueImage;
369     delete valueImage;
370     delete chroImage;
371     for (int i = 0; i < 3; ++i) {
372         delete mode_images_[i];
373     }
374     delete barImage;
375 
376     delete redImage_g;
377     delete greenImage_g;
378     delete blueImage_g;
379     delete valueImage_g;
380     delete chroImage_g;
381     delete barImage_g;
382 
383 }
384 
showRGBBar()385 void HistogramPanel::showRGBBar()
386 {
387     histogramRGBAreaHori->set_visible(
388         histogramRGBArea == histogramRGBAreaHori.get() && showBAR->get_active());
389     histogramRGBAreaVert->set_visible(
390         histogramRGBArea == histogramRGBAreaVert.get() && showBAR->get_active());
391     histogramRGBAreaHori->setShow(false);
392     histogramRGBAreaVert->setShow(false);
393 
394     if (!histogramRGBArea) {
395         return;
396     }
397 
398     setHistRGBInvalid();
399     histogramRGBArea->setShow(showBAR->get_active());
400 }
401 
resized(Gtk::Allocation & req)402 void HistogramPanel::resized (Gtk::Allocation& req)
403 {
404     static int old_height = 0;
405     static int old_width = 0;
406 
407     bool size_changed =
408         old_height != req.get_height() || old_width != req.get_width();
409 
410     if (!histogramArea->updatePending() && size_changed) {
411         histogramArea->updateBackBuffer ();
412         histogramArea->queue_draw ();
413     }
414 
415     // set histogramRGBArea invalid;
416     if (histogramRGBArea && size_changed) {
417         histogramRGBArea->updateBackBuffer(-1, -1, -1);
418         histogramRGBArea->queue_draw ();
419     }
420 
421     // Store current height of the histogram
422     options.histogramHeight = get_height();
423 
424     old_height = req.get_height();
425     old_width = req.get_width();
426 }
427 
red_toggled()428 void HistogramPanel::red_toggled ()
429 {
430     showRed->set_image(showRed->get_active() ? *redImage : *redImage_g);
431     rgbv_toggled();
432 }
green_toggled()433 void HistogramPanel::green_toggled ()
434 {
435     showGreen->set_image(showGreen->get_active() ? *greenImage : *greenImage_g);
436     rgbv_toggled();
437 }
blue_toggled()438 void HistogramPanel::blue_toggled ()
439 {
440     showBlue->set_image(showBlue->get_active() ? *blueImage : *blueImage_g);
441     rgbv_toggled();
442 }
value_toggled()443 void HistogramPanel::value_toggled ()
444 {
445     removeIfThere(showValue, valueImage, false);
446     removeIfThere(showValue, valueImage_g, false);
447     showValue->set_image(showValue->get_active() ? *valueImage : *valueImage_g);
448     rgbv_toggled();
449 }
chro_toggled()450 void HistogramPanel::chro_toggled ()
451 {
452     removeIfThere(showChro, chroImage, false);
453     removeIfThere(showChro, chroImage_g, false);
454     showChro->set_image(showChro->get_active() ? *chroImage : *chroImage_g);
455     rgbv_toggled();
456 }
457 
mode_released()458 void HistogramPanel::mode_released ()
459 {
460     options.histogramDrawMode = (options.histogramDrawMode + 1) % 3;
461     toggleButtonMode();
462     rgbv_toggled();
463 }
464 
brightnessWidgetValueChanged(void)465 void HistogramPanel::brightnessWidgetValueChanged(void)
466 {
467     ConnectionBlocker blocker(brightness_changed_connection);
468     histogramArea->setBrightness(exp(brightnessWidget->get_value()));
469     options.histogramTraceBrightness = histogramArea->getBrightness();
470 }
471 
brightnessUpdated(float brightness)472 void HistogramPanel::brightnessUpdated(float brightness)
473 {
474     brightnessWidget->set_value(log(brightness));
475     options.histogramTraceBrightness = histogramArea->getBrightness();
476 }
477 
scopeOptionsToggled()478 void HistogramPanel::scopeOptionsToggled()
479 {
480     options.histogramShowOptionButtons = scopeOptions->get_active();
481     optionButtons->set_visible(scopeOptions->get_active());
482 }
483 
type_selected(Gtk::RadioButton * button)484 void HistogramPanel::type_selected(Gtk::RadioButton* button)
485 {
486     ScopeType new_type = ScopeType::NONE;
487 
488     if (button == scopeHistBtn) {
489         new_type = ScopeType::HISTOGRAM;
490     } else if (button == scopeHistRawBtn) {
491         new_type = ScopeType::HISTOGRAM_RAW;
492     } else if (button == scopeParadeBtn) {
493         new_type = ScopeType::PARADE;
494     } else if (button == scopeWaveBtn) {
495         new_type = ScopeType::WAVEFORM;
496     } else if (button == scopeVectHcBtn) {
497         new_type = ScopeType::VECTORSCOPE_HC;
498     } else if (button == scopeVectHsBtn) {
499         new_type = ScopeType::VECTORSCOPE_HS;
500     }
501 
502     if (new_type == options.histogramScopeType) {
503         return;
504     }
505 
506     options.histogramScopeType = new_type;
507 
508     type_changed();
509     updateHistAreaOptions();
510     if (histogramRGBArea) {
511         updateHistRGBAreaOptions();
512     }
513     histogramArea->setDirty(true);
514     histogramArea->queue_draw();
515 }
516 
type_changed()517 void HistogramPanel::type_changed()
518 {
519     switch (options.histogramScopeType) {
520         case ScopeType::HISTOGRAM:
521             showRed->show();
522             showGreen->show();
523             showBlue->show();
524             showValue->show();
525             showChro->show();
526             showMode->show();
527             showBAR->show();
528             showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_BAR"));
529             brightnessWidget->hide();
530             histogramRGBArea = histogramRGBAreaHori.get();
531             break;
532         case ScopeType::HISTOGRAM_RAW:
533             showRed->show();
534             showGreen->show();
535             showBlue->show();
536             showValue->hide();
537             showChro->hide();
538             showMode->show();
539             showBAR->hide();
540             brightnessWidget->hide();
541             histogramRGBArea = nullptr;
542             break;
543         case ScopeType::PARADE:
544         case ScopeType::WAVEFORM:
545             showRed->show();
546             showGreen->show();
547             showBlue->show();
548             showValue->show();
549             showChro->hide();
550             showMode->hide();
551             showBAR->show();
552             showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_BAR"));
553             brightnessWidget->show();
554             histogramRGBArea = histogramRGBAreaVert.get();
555             break;
556         case ScopeType::VECTORSCOPE_HC:
557         case ScopeType::VECTORSCOPE_HS:
558             showRed->hide();
559             showGreen->hide();
560             showBlue->hide();
561             showValue->hide();
562             showChro->hide();
563             showMode->hide();
564             showBAR->show();
565             showBAR->set_tooltip_text(M("HISTOGRAM_TOOLTIP_CROSSHAIR"));
566             brightnessWidget->show();
567             histogramRGBArea = nullptr;
568             break;
569         case ScopeType::NONE:
570             break;
571     }
572 
573     if (panel_listener) {
574         updateHistAreaOptions();
575         panel_listener->scopeTypeChanged(options.histogramScopeType);
576     }
577 
578     showRGBBar();
579 }
580 
bar_toggled()581 void HistogramPanel::bar_toggled ()
582 {
583     showBAR->set_image(showBAR->get_active() ? *barImage : *barImage_g);
584     rgbv_toggled();
585     showRGBBar();
586 }
587 
rgbv_toggled()588 void HistogramPanel::rgbv_toggled ()
589 {
590     // Update Display
591     updateHistAreaOptions();
592     histogramArea->updateBackBuffer ();
593     histogramArea->queue_draw ();
594 
595     if (histogramRGBArea) {
596         updateHistRGBAreaOptions();
597         histogramRGBArea->updateBackBuffer(-1, -1, -1);
598         histogramRGBArea->queue_draw ();
599     }
600 }
601 
setHistRGBInvalid()602 void HistogramPanel::setHistRGBInvalid ()
603 {
604     // do something to un-show vertical bars
605     histogramRGBArea->updateBackBuffer(-1, -1, -1);
606     histogramRGBArea->queue_draw ();
607 }
608 
pointerMoved(bool validPos,const Glib::ustring & profile,const Glib::ustring & profileW,int x,int y,int r,int g,int b,bool isRaw)609 void HistogramPanel::pointerMoved (bool validPos, const Glib::ustring &profile, const Glib::ustring &profileW, int x, int y, int r, int g, int b, bool isRaw)
610 {
611     bool update_hist_area = false;
612 
613     if (!validPos) {
614         // do something to un-show vertical bars
615         if (histogramRGBArea) {
616             histogramRGBArea->updateBackBuffer(-1, -1, -1);
617         }
618         update_hist_area = histogramArea->updatePointer(-1, -1, -1);
619     } else {
620         // do something to show vertical bars
621         if (histogramRGBArea) {
622             histogramRGBArea->updateBackBuffer(r, g, b, profile, profileW);
623         }
624         update_hist_area = histogramArea->updatePointer(r, g, b, profile, profileW);
625     }
626     if (histogramRGBArea) {
627         histogramRGBArea->queue_draw();
628     }
629     if (update_hist_area) {
630         histogramArea->queue_draw();
631     }
632 }
633 
634 /*
635  * Move the vertical button bar to the right side
636  * only allowed values for align are Gtk::POS_LEFT and Gtk::POS_RIGHT
637  */
reorder(Gtk::PositionType align)638 void HistogramPanel::reorder (Gtk::PositionType align)
639 {
640     if (align == Gtk::POS_LEFT) {
641         gfxGrid->reference();
642         removeIfThere(this, gfxGrid, false);
643         add (*gfxGrid);
644         gfxGrid->unreference();
645 
646         gfxGrid->remove(*histogramRGBAreaVert);
647         gfxGrid->add(*histogramRGBAreaVert);
648 
649         optionButtons->reference();
650         removeIfThere(buttonGrid, optionButtons, false);
651         buttonGrid->add(*optionButtons);
652         optionButtons->unreference();
653     } else {
654         buttonGrid->reference();
655         removeIfThere(this, buttonGrid, false);
656         add (*buttonGrid);
657         buttonGrid->unreference();
658 
659         gfxGrid->remove(*histogramRGBAreaVert);
660         gfxGrid->attach_next_to(*histogramRGBAreaVert, *histogramArea, Gtk::POS_LEFT, 1, 1);
661 
662         persistentButtons->reference();
663         removeIfThere(buttonGrid, persistentButtons, false);
664         buttonGrid->add(*persistentButtons);
665         persistentButtons->unreference();
666     }
667 }
668 
669 // DrawModeListener interface:
toggleButtonMode()670 void HistogramPanel::toggleButtonMode ()
671 {
672     int m = LIM(options.histogramDrawMode, 0, 2);
673     showMode->set_image(*mode_images_[m]);
674     showMode->set_tooltip_text(Glib::ustring::compose(M("HISTOGRAM_TOOLTIP_MODE"), mode_tips_[m]));
675 }
676 
setPanelListener(HistogramPanelListener * listener)677 void HistogramPanel::setPanelListener(HistogramPanelListener* listener)
678 {
679     panel_listener = listener;
680 
681     if (listener) {
682         listener->scopeTypeChanged(options.histogramScopeType);
683     }
684 }
685 
updateHistAreaOptions()686 void HistogramPanel::updateHistAreaOptions()
687 {
688     histogramArea->updateOptions(
689         showRed->get_active(),
690         showGreen->get_active(),
691         showBlue->get_active(),
692         showValue->get_active(),
693         showChro->get_active(),
694         options.histogramDrawMode,
695         options.histogramScopeType,
696         showBAR->get_active()
697     );
698 }
699 
updateHistRGBAreaOptions()700 void HistogramPanel::updateHistRGBAreaOptions()
701 {
702     histogramRGBArea->updateOptions(
703         showRed->get_active(),
704         showGreen->get_active(),
705         showBlue->get_active(),
706         showValue->get_active(),
707         showChro->get_active(),
708         showBAR->get_active()
709     );
710 }
711 
712 //
713 //
714 //
715 // HistogramScaling
716 
HistogramScaling()717 HistogramScaling::HistogramScaling():
718     factor(10.0)
719 {
720     factor = options.histogram_scaling_factor;
721 }
722 
723 
log(double vsize,double val)724 double HistogramScaling::log(double vsize, double val)
725 {
726     //double factor = 10.0; // can be tuned if necessary - higher is flatter curve
727     return vsize * std::log(factor / (factor + val)) / std::log(factor / (factor + vsize));
728 }
729 
730 //
731 //
732 //
733 // HistogramRGBArea
HistogramRGBArea()734 HistogramRGBArea::HistogramRGBArea () :
735     val(0), r(0), g(0), b(0), valid(false),
736     needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue),
737     needLuma(options.histogramLuma), needChroma(options.histogramChroma),
738     showMode(options.histogramBar), barDisplayed(options.histogramBar), parent(nullptr)
739 {
740     get_style_context()->add_class("drawingarea");
741     set_name("HistogramRGBArea");
742 
743     harih = new HistogramRGBAreaIdleHelper;
744     harih->harea = this;
745     harih->destroyed = false;
746     harih->pending = 0;
747 }
748 
~HistogramRGBArea()749 HistogramRGBArea::~HistogramRGBArea ()
750 {
751     idle_register.destroy();
752 
753     if (harih->pending) {
754         harih->destroyed = true;
755     } else {
756         delete harih;
757     }
758 }
759 
760 
getPreferredThickness(int & min_thickness,int & natural_thickness) const761 void HistogramRGBArea::getPreferredThickness(int& min_thickness, int& natural_thickness) const
762 {
763     int minimumLength = 0;
764     int naturalLength = 0;
765     getPreferredLength(minimumLength, naturalLength);
766     getPreferredThicknessForLength(minimumLength, min_thickness, natural_thickness);
767 }
768 
getPreferredLength(int & min_length,int & natural_length) const769 void HistogramRGBArea::getPreferredLength(int& min_length, int& natural_length) const
770 {
771     int s = RTScalable::getScale();
772     min_length = 60 * s;
773     natural_length = 200 * s;
774 }
775 
getPreferredThicknessForLength(int length,int & min_thickness,int & natural_thickness) const776 void HistogramRGBArea::getPreferredThicknessForLength(int length, int& min_thickness, int& natural_thickness) const
777 {
778     int bThickness = length / 30;
779 
780     int s = RTScalable::getScale();
781 
782     if (bThickness > (10 * s)) {
783         bThickness = 10 * s;
784     } else if (bThickness < (5 * s)) {
785         bThickness = 5 * s;
786     }
787 
788     min_thickness = bThickness;
789     natural_thickness = bThickness;
790 }
791 
792 // unused?
getPreferredLengthForThickness(int thickness,int & min_length,int & natural_length) const793 void HistogramRGBArea::getPreferredLengthForThickness(int thickness, int& min_length, int& natural_length) const
794 {
795     getPreferredLength(min_length, natural_length);
796 }
797 
getShow()798 bool HistogramRGBArea::getShow()
799 {
800     return(showMode);
801 }
802 
setShow(bool show)803 void HistogramRGBArea::setShow(bool show)
804 {
805     showMode = show;
806 }
807 
updateBackBuffer(int r,int g,int b,const Glib::ustring & profile,const Glib::ustring & profileW)808 void HistogramRGBArea::updateBackBuffer (int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW)
809 {
810     if (!get_realized () || !showMode || !(
811         options.histogramScopeType == ScopeType::HISTOGRAM
812         || options.histogramScopeType == ScopeType::PARADE
813         || options.histogramScopeType == ScopeType::WAVEFORM
814     )) {
815         return;
816     }
817 
818     // Mostly not necessary, but should be in some case
819     GThreadLock lock; // All GUI access from idle_add callbacks or separate thread HAVE to be protected
820 
821     Glib::RefPtr<Gdk::Window> window = get_window();
822     int winx, winy, winw, winh;
823     window->get_geometry(winx, winy, winw, winh);
824 
825     double s = RTScalable::getScale();
826 
827     // This will create or update the size of the BackBuffer::surface
828     setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, winw, winh, true);
829 
830     if (surface)  {
831         Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(surface);
832 
833         cc->set_source_rgba (0., 0., 0., 0.);
834         cc->set_operator (Cairo::OPERATOR_CLEAR);
835         cc->paint ();
836         cc->set_operator (Cairo::OPERATOR_OVER);
837 
838         cc->set_antialias(Cairo::ANTIALIAS_NONE);
839         cc->set_line_width (1.0 * s);
840 
841         if ( r != -1 && g != -1 && b != -1 ) {
842             if (needRed) {
843                 // Red
844                 cc->set_source_rgb(rgb_R[0], rgb_R[1], rgb_R[2]);
845                 drawBar(cc, r, 255.0, winw, winh, s);
846             }
847 
848             if (needGreen) {
849                 // Green
850                 cc->set_source_rgb(rgb_G[0], rgb_G[1], rgb_G[2]);
851                 drawBar(cc, g, 255.0, winw, winh, s);
852             }
853 
854             if (needBlue) {
855                 // Blue
856                 cc->set_source_rgb(rgb_B[0], rgb_B[1], rgb_B[2]);
857                 drawBar(cc, b, 255.0, winw, winh, s);
858             }
859 
860             if(
861                 (needLuma || needChroma)
862                 && (options.histogramScopeType == ScopeType::HISTOGRAM
863                     || options.histogramScopeType == ScopeType::PARADE
864                     || options.histogramScopeType == ScopeType::WAVEFORM)
865             ) {
866                 float Lab_L, Lab_a, Lab_b;
867                 rtengine::Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, Lab_L, Lab_a, Lab_b, options.rtSettings.HistogramWorking);
868 
869                 if (needLuma) {
870                     // Luma
871                     cc->set_source_rgb(1.0, 1.0, 1.0);
872                     drawBar(cc, Lab_L, 100.0, winw, winh, s);
873                 }
874 
875                 if (needChroma && options.histogramScopeType == ScopeType::HISTOGRAM) {
876                     // Chroma
877                     double chromaval = sqrt(Lab_a * Lab_a + Lab_b * Lab_b) / 1.8;
878                     cc->set_source_rgb(0.9, 0.9, 0.0);
879                     drawBar(cc, chromaval, 100.0, winw, winh, s);
880                 }
881             }
882         }
883     }
884 
885     setDirty(false);
886 }
887 
update(int valh,int rh,int gh,int bh)888 void HistogramRGBArea::update (int valh, int rh, int  gh, int bh)
889 {
890 
891     if (valh) {
892         val = valh;
893         r = rh;
894         g = gh;
895         b = bh;
896         valid = true;
897     } else {
898         valid = false;
899     }
900 
901     harih->pending++;
902 
903     idle_register.add(
904         [this]() -> bool
905         {
906             if (harih->destroyed) {
907                 if (harih->pending == 1) {
908                     delete harih;
909                 } else {
910                     --harih->pending;
911                 }
912 
913                 return false;
914             }
915 
916             harih->harea->updateBackBuffer(-1, -1, -1);
917             harih->harea->queue_draw ();
918 
919             --harih->pending;
920 
921             return false;
922         }
923     );
924 }
925 
updateOptions(bool r,bool g,bool b,bool l,bool c,bool bar)926 void HistogramRGBArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool bar)
927 {
928 
929     options.histogramRed    = needRed    = r;
930     options.histogramGreen  = needGreen  = g;
931     options.histogramBlue   = needBlue   = b;
932     options.histogramLuma   = needLuma   = l;
933     options.histogramChroma = needChroma = c;
934     options.histogramBar    = showMode   = bar;
935 
936 }
937 
on_realize()938 void HistogramRGBArea::on_realize ()
939 {
940 
941     Gtk::DrawingArea::on_realize();
942     add_events(Gdk::BUTTON_PRESS_MASK);
943 }
944 
on_draw(const::Cairo::RefPtr<Cairo::Context> & cr)945 bool HistogramRGBArea::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
946 {
947 
948     const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
949     style->render_background(cr, 0, 0, get_width(), get_height());
950 
951     // on_realize & updateBackBuffer have to be called before
952     if (surface) {
953         if (isDirty()) { // not sure this could happen...
954             updateBackBuffer(-1, -1, -1);
955         }
956 
957         copySurface(cr, NULL);
958     }
959 
960     style->render_frame (cr, 0, 0, get_width(), get_height());
961 
962     return true;
963 }
964 
on_button_press_event(GdkEventButton * event)965 bool HistogramRGBArea::on_button_press_event (GdkEventButton* event)
966 {
967 
968     if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
969         // do something?
970     }
971 
972     return true;
973 }
974 
factorChanged(double newFactor)975 void HistogramRGBArea::factorChanged (double newFactor)
976 {
977     factor = newFactor;
978     options.histogram_scaling_factor = factor;
979 }
980 
drawBar(Cairo::RefPtr<Cairo::Context> cc,double value,double max_value,int winw,int winh,double scale)981 void HistogramRGBAreaHori::drawBar(Cairo::RefPtr<Cairo::Context> cc, double value, double max_value, int winw, int winh, double scale)
982 {
983     double pos;
984     if (options.histogramDrawMode < 2) {
985         pos = padding + value * (winw - padding * 2.0) / max_value + 0.5 * scale;
986     } else {
987         pos = padding + HistogramScaling::log (max_value, value) * (winw - padding * 2.0) / max_value + 0.5 * scale;
988     }
989     cc->move_to(pos, 0.0);
990     cc->line_to(pos, winh - 0.0);
991     cc->stroke();
992 }
993 
get_request_mode_vfunc() const994 Gtk::SizeRequestMode HistogramRGBAreaHori::get_request_mode_vfunc () const
995 {
996     return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
997 }
998 
get_preferred_height_vfunc(int & minimum_height,int & natural_height) const999 void HistogramRGBAreaHori::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
1000 {
1001     getPreferredThickness(minimum_height, natural_height);
1002 }
1003 
get_preferred_width_vfunc(int & minimum_width,int & natural_width) const1004 void HistogramRGBAreaHori::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
1005 {
1006     getPreferredLength(minimum_width, natural_width);
1007 }
1008 
get_preferred_height_for_width_vfunc(int width,int & minimum_height,int & natural_height) const1009 void HistogramRGBAreaHori::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
1010 {
1011     getPreferredThicknessForLength(width, minimum_height, natural_height);
1012 }
1013 
get_preferred_width_for_height_vfunc(int height,int & minimum_width,int & natural_width) const1014 void HistogramRGBAreaHori::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
1015 {
1016     getPreferredLengthForThickness(height, minimum_width, natural_width);
1017 }
1018 
drawBar(Cairo::RefPtr<Cairo::Context> cc,double value,double max_value,int winw,int winh,double scale)1019 void HistogramRGBAreaVert::drawBar(Cairo::RefPtr<Cairo::Context> cc, double value, double max_value, int winw, int winh, double scale)
1020 {
1021     double pos;
1022     if (options.histogramDrawMode < 2 || options.histogramScopeType == ScopeType::PARADE || options.histogramScopeType == ScopeType::WAVEFORM) {
1023         pos = padding + value * (winh - padding * 2.0 - 1) / max_value + 0.5 * scale;
1024     } else {
1025         pos = padding + HistogramScaling::log (max_value, value) * (winh - padding * 2.0) / max_value + 0.5 * scale;
1026     }
1027     cc->move_to(0.0, winh - pos);
1028     cc->line_to(winw, winh - pos);
1029     cc->stroke();
1030 }
1031 
get_request_mode_vfunc() const1032 Gtk::SizeRequestMode HistogramRGBAreaVert::get_request_mode_vfunc () const
1033 {
1034     return Gtk::SIZE_REQUEST_WIDTH_FOR_HEIGHT;
1035 }
1036 
get_preferred_height_vfunc(int & minimum_height,int & natural_height) const1037 void HistogramRGBAreaVert::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
1038 {
1039     getPreferredLength(minimum_height, natural_height);
1040 }
1041 
get_preferred_width_vfunc(int & minimum_width,int & natural_width) const1042 void HistogramRGBAreaVert::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
1043 {
1044     minimum_width = 10 * RTScalable::getScale();
1045     natural_width = minimum_width;
1046 }
1047 
get_preferred_height_for_width_vfunc(int width,int & minimum_height,int & natural_height) const1048 void HistogramRGBAreaVert::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
1049 {
1050     getPreferredLengthForThickness(width, minimum_height, natural_height);
1051 }
1052 
get_preferred_width_for_height_vfunc(int height,int & minimum_width,int & natural_width) const1053 void HistogramRGBAreaVert::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
1054 {
1055     get_preferred_width_vfunc(minimum_width, natural_width);
1056 }
1057 
1058 //
1059 //
1060 //
1061 // HistogramArea
HistogramArea(DrawModeListener * fml,bool is_main)1062 HistogramArea::HistogramArea(DrawModeListener *fml, bool is_main):
1063     vectorscope_scale(0),
1064     vect_hc(0, 0), vect_hs(0, 0),
1065     vect_hc_buffer_dirty(true), vect_hs_buffer_dirty(true),
1066     waveform_scale(0),
1067     rwave(0, 0), gwave(0, 0), bwave(0, 0), lwave(0, 0),
1068     parade_buffer_r_dirty(true), parade_buffer_g_dirty(true), parade_buffer_b_dirty(true),
1069     wave_buffer_dirty(true), wave_buffer_luma_dirty(true),
1070     valid(false), drawMode(options.histogramDrawMode), myDrawModeListener(fml),
1071     scopeType(options.histogramScopeType),
1072     oldwidth(-1), oldheight(-1),
1073     trace_brightness(1.0),
1074     needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue),
1075     needLuma(options.histogramLuma), needChroma(options.histogramChroma),
1076     isPressed(false), movingPosition(0.0),
1077     needPointer(options.histogramBar),
1078     pointer_red(-1), pointer_green(-1), pointer_blue(-1),
1079     pointer_a(0), pointer_b(0),
1080     is_main_(is_main)
1081 {
1082 
1083     rhist(256);
1084     ghist(256);
1085     bhist(256);
1086     lhist(256);
1087     chist(256);
1088 
1089     get_style_context()->add_class("drawingarea");
1090     set_name("HistogramArea");
1091 
1092     haih = new HistogramAreaIdleHelper;
1093     haih->harea = this;
1094     haih->destroyed = false;
1095     haih->pending = 0;
1096 }
1097 
~HistogramArea()1098 HistogramArea::~HistogramArea ()
1099 {
1100     idle_register.destroy();
1101 
1102     if (haih->pending) {
1103         haih->destroyed = true;
1104     } else {
1105         delete haih;
1106     }
1107 }
1108 
get_request_mode_vfunc() const1109 Gtk::SizeRequestMode HistogramArea::get_request_mode_vfunc () const
1110 {
1111     return Gtk::SIZE_REQUEST_CONSTANT_SIZE;
1112 }
1113 
get_preferred_height_vfunc(int & minimum_height,int & natural_height) const1114 void HistogramArea::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
1115 {
1116     int s = RTScalable::getScale();
1117     minimum_height = 100 * s;
1118     natural_height = 200 * s;
1119 }
1120 
get_preferred_width_vfunc(int & minimum_width,int & natural_width) const1121 void HistogramArea::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
1122 {
1123 
1124     int s = RTScalable::getScale();
1125     minimum_width = 200 * s;
1126     natural_width = 400 * s;
1127 }
1128 
get_preferred_height_for_width_vfunc(int width,int & minimum_height,int & natural_height) const1129 void HistogramArea::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
1130 {
1131 
1132     minimum_height = 0;
1133     natural_height = 0;
1134 }
1135 
get_preferred_width_for_height_vfunc(int height,int & minimum_width,int & natural_width) const1136 void HistogramArea::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
1137 {
1138     get_preferred_width_vfunc (minimum_width, natural_width);
1139 }
1140 
updateOptions(bool r,bool g,bool b,bool l,bool c,int mode,ScopeType type,bool pointer)1141 void HistogramArea::updateOptions (bool r, bool g, bool b, bool l, bool c, int mode, ScopeType type, bool pointer)
1142 {
1143     wave_buffer_dirty = wave_buffer_dirty || needRed != r || needGreen != g || needBlue != b;
1144 
1145     needRed = r;
1146     needGreen = g;
1147     needBlue = b;
1148     needLuma = l;
1149     needChroma = c;
1150     drawMode = mode;
1151     scopeType = type;
1152     needPointer = pointer;
1153 
1154     if (is_main_) {
1155         options.histogramRed = needRed;
1156         options.histogramGreen = needGreen;
1157         options.histogramBlue = needBlue;
1158         options.histogramLuma = needLuma;
1159         options.histogramChroma = needChroma;
1160         options.histogramDrawMode = drawMode;
1161         options.histogramScopeType = scopeType;
1162         options.histogramBar = needPointer;
1163     }
1164 }
1165 
updatePending(void)1166 bool HistogramArea::updatePending(void)
1167 {
1168     return haih->pending > 0 && !haih->destroyed;
1169 }
1170 
update(const LUTu & histRed,const LUTu & histGreen,const LUTu & histBlue,const LUTu & histLuma,const LUTu & histChroma,const LUTu & histRedRaw,const LUTu & histGreenRaw,const LUTu & histBlueRaw,int vectorscopeScale,const array2D<int> & vectorscopeHC,const array2D<int> & vectorscopeHS,int waveformScale,const array2D<int> & waveformRed,const array2D<int> & waveformGreen,const array2D<int> & waveformBlue,const array2D<int> & waveformLuma)1171 void HistogramArea::update(
1172     const LUTu& histRed,
1173     const LUTu& histGreen,
1174     const LUTu& histBlue,
1175     const LUTu& histLuma,
1176     const LUTu& histChroma,
1177     const LUTu& histRedRaw,
1178     const LUTu& histGreenRaw,
1179     const LUTu& histBlueRaw,
1180     int vectorscopeScale,
1181     const array2D<int>& vectorscopeHC,
1182     const array2D<int>& vectorscopeHS,
1183     int waveformScale,
1184     const array2D<int>& waveformRed,
1185     const array2D<int>& waveformGreen,
1186     const array2D<int>& waveformBlue,
1187     const array2D<int>& waveformLuma
1188 )
1189 {
1190     if (histRed) {
1191         switch (scopeType) {
1192             case ScopeType::HISTOGRAM:
1193                 rhist = histRed;
1194                 ghist = histGreen;
1195                 bhist = histBlue;
1196                 lhist = histLuma;
1197                 chist = histChroma;
1198                 break;
1199             case ScopeType::HISTOGRAM_RAW:
1200                 rhistRaw = histRedRaw;
1201                 ghistRaw = histGreenRaw;
1202                 bhistRaw = histBlueRaw;
1203                 break;
1204             case ScopeType::PARADE:
1205             case ScopeType::WAVEFORM:
1206                 waveform_scale = waveformScale;
1207                 set_arr(rwave, waveformRed);
1208                 set_arr(gwave, waveformGreen);
1209                 set_arr(bwave, waveformBlue);
1210                 set_arr(lwave, waveformLuma);
1211                 parade_buffer_r_dirty = parade_buffer_g_dirty = parade_buffer_b_dirty = wave_buffer_dirty = wave_buffer_luma_dirty = true;
1212                 break;
1213             case ScopeType::VECTORSCOPE_HS:
1214                 vectorscope_scale = vectorscopeScale;
1215                 set_arr(vect_hs, vectorscopeHS);
1216                 vect_hs_buffer_dirty = true;
1217                 break;
1218             case ScopeType::VECTORSCOPE_HC:
1219                 vectorscope_scale = vectorscopeScale;
1220                 set_arr(vect_hc, vectorscopeHC);
1221                 vect_hc_buffer_dirty = true;
1222                 break;
1223             case ScopeType::NONE:
1224                 break;
1225         }
1226         valid = true;
1227     } else {
1228         valid = false;
1229     }
1230 
1231     haih->pending++;
1232 
1233     // Can be done outside of the GUI thread
1234     idle_register.add(
1235         [this]() -> bool
1236         {
1237             if (haih->destroyed) {
1238                 if (haih->pending == 1) {
1239                     delete haih;
1240                 } else {
1241                     --haih->pending;
1242                 }
1243 
1244                 return false;
1245             }
1246 
1247             haih->harea->setDirty(true);
1248             haih->harea->updateBackBuffer();
1249             haih->harea->queue_draw();
1250 
1251             --haih->pending;
1252 
1253             return false;
1254         }
1255     );
1256 }
1257 
updateBackBuffer(int custom_w,int custom_h)1258 void HistogramArea::updateBackBuffer(int custom_w, int custom_h)
1259 {
1260     int winw = custom_w;
1261     int winh = custom_h;
1262     if (winw < 0) {
1263         if (!get_realized ()) {
1264             return;
1265         }
1266 
1267         Glib::RefPtr<Gdk::Window> window = get_window();
1268         int winx, winy;
1269         window->get_geometry(winx, winy, winw, winh);
1270     }
1271 
1272     // This will create or update the size of the BackBuffer::surface
1273     setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, winw, winh, true);
1274 
1275     Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create(surface);
1276     const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
1277 
1278     double s = RTScalable::getScale();
1279 
1280     // Setup drawing
1281     cr->set_source_rgba (0., 0., 0., 0.);
1282     cr->set_operator (Cairo::OPERATOR_CLEAR);
1283     cr->paint ();
1284 
1285     cr->set_operator (Cairo::OPERATOR_SOURCE);
1286 
1287     // Prepare drawing gridlines first
1288     cr->set_source_rgba (1., 1., 1., 0.25);
1289     cr->set_line_width (1.0 * s);
1290     cr->set_antialias(Cairo::ANTIALIAS_NONE);
1291     cr->set_line_join(Cairo::LINE_JOIN_MITER);
1292     cr->set_line_cap(Cairo::LINE_CAP_BUTT);
1293     std::valarray<double> ch_ds (1);
1294     ch_ds[0] = 4;
1295     cr->set_dash (ch_ds, 0);
1296 
1297     bool rawMode = (scopeType == ScopeType::HISTOGRAM_RAW);
1298     if (rawMode) {
1299         updateRaw(cr);
1300     } else {
1301         updateNonRaw(cr);
1302     }
1303 
1304     // Draw the frame's border
1305     style->render_frame(cr, 0, 0, surface->get_width(), surface->get_height());
1306 
1307     oldwidth = w;
1308     oldheight = h;
1309 
1310     setDirty(false);
1311 }
1312 
1313 
updateNonRaw(Cairo::RefPtr<Cairo::Context> cr)1314 void HistogramArea::updateNonRaw(Cairo::RefPtr<Cairo::Context> cr)
1315 {
1316     set_has_tooltip(false);
1317 
1318     const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
1319 
1320     double s = RTScalable::getScale();
1321 
1322     // determine the number of h-gridlines based on current h
1323     int nrOfHGridPartitions = (int)rtengine::min (16.0, pow (2.0, floor ((h - 100) / 250) + 2));
1324     int nrOfVGridPartitions = 8; // always show 8 stops (lines at 1,3,7,15,31,63,127)
1325 
1326     // draw vertical gridlines
1327     if (scopeType == ScopeType::HISTOGRAM) {
1328         if (drawMode == 0) {
1329             for (int i = 1; i < nrOfVGridPartitions; i++) {
1330                 cr->move_to((pow(2.0,i) - 1) / 255.0 * w + 0.5, 0.);
1331                 cr->line_to((pow(2.0,i) - 1) / 255.0 * w + 0.5, h);
1332                 cr->stroke();
1333             }
1334         } else {
1335             for (int i = 1; i < nrOfVGridPartitions; i++) {
1336                 cr->move_to(HistogramScaling::log(255, pow(2.0,i) - 1) / 255.0 * w + 0.5, 0.);
1337                 cr->line_to(HistogramScaling::log(255, pow(2.0,i) - 1) / 255.0 * w + 0.5, h);
1338                 cr->stroke();
1339             }
1340         }
1341     }
1342 
1343     // draw horizontal gridlines
1344     if (scopeType == ScopeType::PARADE || scopeType == ScopeType::WAVEFORM) {
1345         for (int i = 0; i <= nrOfVGridPartitions; i++) {
1346             const double ypos = h - padding - (pow(2.0,i) - 1) * (h - 2 * padding - 1) / 255.0;
1347             cr->move_to(0, ypos);
1348             cr->line_to(w, ypos);
1349             cr->stroke();
1350         }
1351     } else if (scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS) {
1352         // Vectorscope has no gridlines.
1353     } else if (drawMode != 2) {
1354         for (int i = 1; i < nrOfHGridPartitions; i++) {
1355             cr->move_to (0., i * (double)h / nrOfHGridPartitions + 0.5);
1356             cr->line_to (w, i * (double)h / nrOfHGridPartitions + 0.5);
1357             cr->stroke ();
1358         }
1359     } else {
1360         for (int i = 1; i < nrOfHGridPartitions; i++) {
1361             cr->move_to (0., h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5*s);
1362             cr->line_to (w, h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5*s);
1363             cr->stroke ();
1364         }
1365     }
1366 
1367     cr->unset_dash();
1368 
1369     if (valid && scopeType == ScopeType::HISTOGRAM) {
1370         // For RAW mode use the other hists
1371         LUTu& rh = rhist;
1372         LUTu& gh = ghist;
1373         LUTu& bh = bhist;
1374 
1375         // make copies of LUT for faster access
1376         unsigned int lhisttemp[256] ALIGNED16 {0}, chisttemp[256] ALIGNED16 {0}, rhtemp[256] ALIGNED16 {0}, ghtemp[256] ALIGNED16 {0}, bhtemp[256] ALIGNED16 {0};
1377         const int scale = 1;
1378 
1379         for(int i = 0; i < 256; i++) {
1380             if(needLuma) {
1381                 lhisttemp[i] = lhist[i];
1382             }
1383 
1384             if(needChroma) {
1385                 chisttemp[i] = chist[i];
1386             }
1387 
1388             if(needRed) {
1389                 rhtemp[i] = rh[i];
1390             }
1391 
1392             if(needGreen) {
1393                 ghtemp[i] = gh[i];
1394             }
1395 
1396             if(needBlue) {
1397                 bhtemp[i] = bh[i];
1398             }
1399         }
1400 
1401         // Compute the highest point of the histogram for scaling
1402         // Values at far left and right end (0 and 255) are handled differently
1403 
1404         unsigned int histheight = 0;
1405 
1406         for (int i = 1; i < 255; i++) {
1407             if (needLuma && lhisttemp[i] > histheight) {
1408                 histheight = lhisttemp[i];
1409             }
1410 
1411             if (needChroma && chisttemp[i] > histheight) {
1412                 histheight = chisttemp[i];
1413             }
1414 
1415             if (needRed && rhtemp[i] > histheight) {
1416                 histheight = rhtemp[i];
1417             }
1418 
1419             if (needGreen && ghtemp[i] > histheight) {
1420                 histheight = ghtemp[i];
1421             }
1422 
1423             if (needBlue && bhtemp[i] > histheight) {
1424                 histheight = bhtemp[i];
1425             }
1426         }
1427 
1428         int realhistheight = histheight;
1429 
1430         if (realhistheight < h - 2) {
1431             realhistheight = h - 2;
1432         }
1433 
1434         cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
1435         cr->set_line_width (1.0 * s);
1436         cr->set_operator (Cairo::OPERATOR_OVER);
1437 
1438         int ui = 0, oi = 0;
1439 
1440         const bool rawMode = (scopeType == ScopeType::HISTOGRAM_RAW);
1441         if (needLuma && !rawMode) {
1442             drawCurve(cr, lhist, realhistheight, w, h);
1443             cr->set_source_rgba (0.65, 0.65, 0.65, 0.65);
1444             cr->fill ();
1445             drawMarks(cr, lhist, scale, w, ui, oi);
1446         }
1447 
1448         if (needChroma && !rawMode) {
1449             drawCurve(cr, chist, realhistheight, w, h);
1450             cr->set_source_rgb (0.9, 0.9, 0.);
1451             cr->stroke ();
1452             drawMarks(cr, chist, scale, w, ui, oi);
1453         }
1454 
1455         if (needRed) {
1456             drawCurve(cr, rh, realhistheight, w, h);
1457             cr->set_source_rgb(rgb_R[0], rgb_R[1], rgb_R[2]);
1458             cr->stroke ();
1459             drawMarks(cr, rh, scale, w, ui, oi);
1460         }
1461 
1462         if (needGreen) {
1463             drawCurve(cr, gh, realhistheight, w, h);
1464             cr->set_source_rgb(rgb_G[0], rgb_G[1], rgb_G[2]);
1465             cr->stroke ();
1466             drawMarks(cr, gh, scale, w, ui, oi);
1467         }
1468 
1469         if (needBlue) {
1470             drawCurve(cr, bh, realhistheight, w, h);
1471             cr->set_source_rgb(rgb_B[0], rgb_B[1], rgb_B[2]);
1472             cr->stroke ();
1473             drawMarks(cr, bh, scale, w, ui, oi);
1474         }
1475 
1476     } else if (scopeType == ScopeType::PARADE && rwave.width() > 0) {
1477         drawParade(cr, w, h);
1478     } else if (scopeType == ScopeType::WAVEFORM && rwave.width() > 0) {
1479         drawWaveform(cr, w, h);
1480     } else if (scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS) {
1481         drawVectorscope(cr, w, h);
1482     }
1483 }
1484 
1485 
1486 namespace {
1487 
1488 class RawIdxHelper {
1489 public:
RawIdxHelper(bool logscale,unsigned int ub,unsigned int width)1490     RawIdxHelper(bool logscale, unsigned int ub, unsigned int width):
1491         logscale_(logscale),
1492         ub_(ub)
1493     {
1494         incr_ = std::max(ub_ / width, 1u);
1495     }
1496 
operator ()(unsigned int & cur,unsigned int & i) const1497     void operator()(unsigned int &cur, unsigned int &i) const
1498     {
1499         unsigned int j = i;
1500         i = cur;
1501         unsigned int next = cur;
1502         if (logscale_) {
1503             next = std::max(static_cast<unsigned int>(j * 1.3f), cur + 1u);
1504         } else {
1505             next = cur + incr_;
1506         }
1507         if (ub_ - next < next - cur) {
1508             next = ub_ + 1;
1509         }
1510         if (cur < ub_ && next > ub_) {
1511             cur = ub_;
1512         } else {
1513             cur = std::min(next, ub_+1);
1514         }
1515     }
1516 
1517 private:
1518     bool logscale_;
1519     unsigned int ub_;
1520     unsigned int incr_;
1521 };
1522 
1523 } // namespace
1524 
updatePointer(int r,int g,int b,const Glib::ustring & profile,const Glib::ustring & profileW)1525 bool HistogramArea::updatePointer(int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW)
1526 {
1527     if (!needPointer || !(scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS)) {
1528         return false;
1529     }
1530     if (pointer_red == r && pointer_green == g && pointer_blue == b) {
1531         return false;
1532     }
1533 
1534     float L;
1535     pointer_red = r;
1536     pointer_green = g;
1537     pointer_blue = b;
1538     Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, L, pointer_a, pointer_b, options.rtSettings.HistogramWorking);
1539     updateBackBuffer();
1540     return true;
1541 }
1542 
updateRaw(Cairo::RefPtr<Cairo::Context> cr)1543 void HistogramArea::updateRaw(Cairo::RefPtr<Cairo::Context> cr)
1544 {
1545     const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
1546     double s = RTScalable::getScale();
1547 
1548     // determine the number of h-gridlines based on current h
1549     int nrOfHGridPartitions = (int)rtengine::min (16.0, pow (2.0, floor ((h - 100) / 250) + 2));
1550     double sz = 2;
1551     if (valid) {
1552         sz = rtengine::max(rhistRaw.getUpperBound(), ghistRaw.getUpperBound(), bhistRaw.getUpperBound());
1553     }
1554 
1555     int minval[3];
1556     int mincount[3];
1557     int maxval[3];
1558     int maxcount[3];
1559     int peakval[3];
1560     int peakcount[3];
1561     int totcount[3];
1562     int totsum[3];
1563     const char *ch[3] = {"#FF0000", "#00FF00", "#0000FF"};
1564 
1565     // draw vertical gridlines
1566     const double logmax = std::log2(sz);
1567     const bool logscale = drawMode > 0;
1568 
1569     for (double i = sz / 2.0; ; i /= 2.0) {
1570         double x = i / sz;
1571         if (logscale) {
1572             x = logmax + std::log2(x);
1573             if (x <= 0) {
1574                 break;
1575             }
1576             x /= logmax;
1577         }
1578         x *= w;
1579         cr->move_to(x, 0.);
1580         cr->line_to(x, h);
1581         cr->stroke();
1582         if (i <= 1.0) {
1583             break;
1584         }
1585     }
1586 
1587     // draw horizontal gridlines
1588     if (drawMode == 2) {
1589         for (int i = 1; i < nrOfHGridPartitions; i++) {
1590             double y = double(i) / nrOfHGridPartitions;
1591             y = rtengine::log2lin(y, 10.0);
1592             cr->move_to(0., y * h);
1593             cr->line_to(w, y * h);
1594             cr->stroke();
1595         }
1596     } else {
1597         for (int i = 1; i < nrOfHGridPartitions; i++) {
1598             cr->move_to(0., i * (double)h / nrOfHGridPartitions + 0.5);
1599             cr->line_to(w, i * (double)h / nrOfHGridPartitions + 0.5);
1600             cr->stroke();
1601         }
1602     }
1603 
1604     cr->unset_dash();
1605 
1606     if (valid) {
1607         // For RAW mode use the other hists
1608         LUTu &rh = rhistRaw;
1609         LUTu &gh = ghistRaw;
1610         LUTu &bh = bhistRaw;
1611 
1612         auto ub = rtengine::max(rh.getUpperBound(), gh.getUpperBound(), bh.getUpperBound());
1613 
1614         const auto bin =
1615             [](LUTu &data, int i, int next) -> double
1616             {
1617                 double val = 0.0;
1618                 for (int j = i; j < next; ++j) {
1619                     val += data[j];
1620                 }
1621                 val /= (next - i);
1622                 return val;
1623             };
1624 
1625         //const int delta = std::max(int(ub / std::max(w / 2, 1)), 1);
1626         //const float delta = 1.05f;
1627         RawIdxHelper next_raw_idx(logscale, ub, w);
1628         unsigned int next = 1;
1629         unsigned int histheight = 0;
1630 
1631         const unsigned int off = drawMode == 2 ? 0 : 1;
1632         for (unsigned int i = off; i <= ub-off;) {
1633             if (i < rh.getSize()) {
1634                 double val = bin(rh, i, next);
1635                 if (histheight < val) {
1636                     histheight = val;
1637                 }
1638             }
1639             if (i < gh.getSize()) {
1640                 double val = bin(gh, i, next);
1641                 if (histheight < val) {
1642                     histheight = val;
1643                 }
1644             }
1645             if (i < bh.getSize()) {
1646                 double val = bin(bh, i, next);
1647                 if (histheight < val) {
1648                     histheight = val;
1649                 }
1650             }
1651 
1652             next_raw_idx(next, i);
1653         }
1654 
1655         int realhistheight = histheight;
1656 
1657         if (realhistheight < h - 2) {
1658             realhistheight = h - 2;
1659         }
1660 
1661         cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
1662         cr->set_line_width (1.0 * s);
1663         cr->set_operator (Cairo::OPERATOR_OVER);
1664 
1665         int ui = 0, oi = 0;
1666 
1667         if (needRed) {
1668             drawRawCurve(cr, rh, ub, realhistheight, w, h);
1669             cr->set_source_rgb(rgb_R[0], rgb_R[1], rgb_R[2]);
1670             cr->stroke();
1671             drawMarks(cr, rh, 1.0, w, ui, oi);
1672         }
1673 
1674         if (needGreen) {
1675             drawRawCurve(cr, gh, ub, realhistheight, w, h);
1676             cr->set_source_rgb(rgb_G[0], rgb_G[1], rgb_G[2]);
1677             cr->stroke();
1678             drawMarks(cr, gh, 1.0, w, ui, oi);
1679         }
1680 
1681         if (needBlue) {
1682             drawRawCurve(cr, bh, ub, realhistheight, w, h);
1683             cr->set_source_rgb(rgb_B[0], rgb_B[1], rgb_B[2]);
1684             cr->stroke();
1685             drawMarks(cr, bh, 1.0, w, ui, oi);
1686         }
1687 
1688         // update stats
1689         for (int c = 0; c < 3; ++c) {
1690             minval[c] = -1;
1691             mincount[c] = 0;
1692             maxval[c] = -1;
1693             maxcount[c] = 0;
1694             peakval[c] = 0;
1695             peakcount[c] = 0;
1696             totcount[c] = 0;
1697             totsum[c] = 0;
1698         }
1699 
1700         const auto update =
1701             [&](int c, LUTu &data, unsigned int i) -> void
1702             {
1703                 if (i < data.getSize()) {
1704                     int cnt = data[i];
1705                     if (cnt > 0) {
1706                         if (minval[c] < 0) {
1707                             minval[c] = i;
1708                             mincount[c] = cnt;
1709                         }
1710                         maxval[c] = i;
1711                         maxcount[c] = cnt;
1712                         if (peakcount[c] < cnt) {
1713                             peakval[c] = i;
1714                             peakcount[c] = cnt;
1715                         }
1716                         ++totcount[c];
1717                     }
1718                     totsum[c] += cnt;
1719                 }
1720             };
1721 
1722         for (unsigned int i = 0; i <= ub; ++i) {
1723             update(0, rh, i);
1724             update(1, gh, i);
1725             update(2, bh, i);
1726         }
1727     }
1728 
1729 
1730     if (valid) {
1731         const auto pct =
1732             [](int n, int d) -> Glib::ustring
1733             {
1734                 double p = double(int(double(n)/double(d) * 10000)) / 100.0;
1735                 if (p == 0 && n > 0) {
1736                     return "&lt;0.01%";
1737                 } else {
1738                     return Glib::ustring::compose("%1%%", p);
1739                 }
1740             };
1741 
1742         set_has_tooltip(true);
1743         Glib::ustring tip = "";
1744         for (int i = 0; i < 3; ++i) {
1745             double ev = minval[i] >= 0 ? std::log2(maxval[i] - minval[i]) : 0;
1746             ev = double(int(ev * 100)) / 100.0;
1747             Glib::ustring m = Glib::ustring::compose(M("HISTOGRAM_RAW_STATS_TOOLTIP"), minval[i], pct(mincount[i], totsum[i]), maxval[i], pct(maxcount[i], totsum[i]), peakval[i], pct(peakcount[i], totsum[i]), ev, totcount[i], totsum[i]);
1748             Glib::ustring s = Glib::ustring::compose("<span font_family=\"Arial\" size=\"larger\" foreground=\"%1\">&#9632;</span> %2", ch[i], m);
1749             if (i > 0) {
1750                 tip += "\n";
1751             }
1752             tip += s;
1753         }
1754         set_tooltip_markup(tip);
1755     } else {
1756         set_has_tooltip(false);
1757     }
1758 }
1759 
1760 
drawRawCurve(Cairo::RefPtr<Cairo::Context> & cr,LUTu & data,unsigned int ub,double scale,int hsize,int vsize)1761 void HistogramArea::drawRawCurve(Cairo::RefPtr<Cairo::Context> &cr,
1762                                  LUTu &data, unsigned int ub, double scale,
1763                                  int hsize, int vsize)
1764 {
1765     double s = RTScalable::getScale();
1766 
1767     cr->set_line_width(s);
1768     cr->move_to(0, vsize - 1);
1769     scale = scale <= 0.f ? 0.001f : scale; // avoid division by zero and negative values
1770 
1771     unsigned int next = 1;
1772     const bool logscale = drawMode > 0;
1773     const double logmax = std::log2(ub);
1774     //const double ybase = std::pow(10, std::max(std::floor(std::log(scale) / std::log(10))-1, 1.0));
1775     const double ylogmax = std::log10(scale);
1776 
1777     RawIdxHelper next_raw_idx(logscale, data.getUpperBound(), hsize);
1778 
1779     for (unsigned int i = logscale ? 1 : 0; i < data.getSize(); ) {
1780         double val = 0.0;
1781         for (unsigned int j = i; j < next; ++j) {
1782             val += data[j];
1783         }
1784         val /= (next - i);
1785 
1786         val = std::min(val / scale, 1.0);
1787 
1788         if (drawMode == 2 && val > 0) { // scale y for log-scale
1789             //val = rtengine::lin2log(val, ybase);
1790             val = std::max((ylogmax + std::log10(val)) / ylogmax, 0.0);
1791         }
1792 
1793         double iscaled = std::min(double(i) / ub, 1.0);
1794         if (logscale) { // scale x for log-scale
1795             iscaled = logmax + std::log2(iscaled);
1796             if (iscaled < 0) {
1797                 next_raw_idx(next, i);
1798                 continue;
1799             }
1800             iscaled /= logmax;
1801         }
1802 
1803         double posX = iscaled * hsize;
1804         double posY = vsize - 2 + val * (4 - vsize);
1805 
1806         cr->line_to(posX, posY);
1807 
1808         next_raw_idx(next, i);
1809     }
1810 
1811     cr->line_to(hsize - 1, vsize - 1);
1812 }
1813 
1814 
on_realize()1815 void HistogramArea::on_realize ()
1816 {
1817 
1818     Gtk::DrawingArea::on_realize();
1819     add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
1820 }
1821 
drawCurve(Cairo::RefPtr<Cairo::Context> & cr,const LUTu & data,double scale,int hsize,int vsize)1822 void HistogramArea::drawCurve(Cairo::RefPtr<Cairo::Context> &cr,
1823                               const LUTu &data, double scale, int hsize, int vsize)
1824 {
1825     double s = RTScalable::getScale();
1826 
1827     cr->set_line_width(s);
1828     cr->move_to (0, vsize - 1);
1829     scale = scale <= 0.f ? 0.001f : scale; // avoid division by zero and negative values
1830 
1831     for (int i = 0; i < 256; i++) {
1832         double val = data[i] * (double)vsize / scale;
1833 
1834         if (drawMode == 2) { // scale y for log-scale
1835             val = HistogramScaling::log ((double)vsize, val);
1836         }
1837 
1838         double iscaled = i;
1839         if (drawMode > 0) { // scale x for log-scale
1840             iscaled = HistogramScaling::log (255.0, (double)i);
1841         }
1842 
1843         double posX = (iscaled / 255.0) * (hsize - 1);
1844         double posY = vsize - 2 + val * (4 - vsize) / vsize;
1845 
1846         cr->line_to (posX, posY);
1847     }
1848 
1849     cr->line_to (hsize - 1, vsize - 1);
1850 }
1851 
drawMarks(Cairo::RefPtr<Cairo::Context> & cr,const LUTu & data,double scale,int hsize,int & ui,int & oi)1852 void HistogramArea::drawMarks(Cairo::RefPtr<Cairo::Context> &cr,
1853                               const LUTu &data, double scale, int hsize, int & ui, int & oi)
1854 {
1855     int s = 8 * RTScalable::getScale();
1856 
1857     if(data[0] > scale) {
1858         cr->rectangle(0, (ui++)*s, s, s);
1859     }
1860 
1861     if(data[data.getUpperBound()] > scale) {
1862         cr->rectangle(hsize - s, (oi++)*s, s, s);
1863     }
1864 
1865     cr->fill();
1866 }
1867 
drawParade(Cairo::RefPtr<Cairo::Context> & cr,int w,int h)1868 void HistogramArea::drawParade(Cairo::RefPtr<Cairo::Context> &cr, int w, int h)
1869 {
1870     // Arbitrary scale factor divided by current scale.
1871     const float scale = trace_brightness * 32.f * 255.f / waveform_scale;
1872     const int wave_width = rwave.width();
1873     const int wave_height = rwave.height();
1874 
1875     // See Cairo documentation on stride.
1876     const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, rwave.width());
1877     const auto buffer_size = static_cast<std::vector<unsigned char>::size_type>(wave_height) * cairo_stride;
1878 
1879     if (parade_buffer_r_dirty && needRed) {
1880         parade_buffer_r.assign(buffer_size, 0);
1881         assert(parade_buffer_r.size() % 4 == 0);
1882 
1883         for (int val = 0; val < wave_height; val++) {
1884             const int* const r_row = rwave[val];
1885             std::uint32_t* const buffer_r_row = reinterpret_cast<uint32_t*>(parade_buffer_r.data() + (255 - val) * cairo_stride);
1886             for (int col = 0; col < wave_width; col++) {
1887                 const unsigned char r = std::min<float>(scale * r_row[col], 0xff);
1888                 if (r != 0) {
1889                     buffer_r_row[col] = (r << 16) | (r << 24);
1890                 }
1891             }
1892         }
1893 
1894         parade_buffer_r_dirty = false;
1895     }
1896 
1897     if (parade_buffer_g_dirty && needGreen) {
1898         parade_buffer_g.assign(buffer_size, 0);
1899         assert(parade_buffer_g.size() % 4 == 0);
1900 
1901         for (int val = 0; val < wave_height; val++) {
1902             const int* const g_row = gwave[val];
1903             std::uint32_t* const buffer_g_row = reinterpret_cast<uint32_t*>(parade_buffer_g.data() + (255 - val) * cairo_stride);
1904             for (int col = 0; col < wave_width; col++) {
1905                 const unsigned char g = std::min<float>(scale * g_row[col], 0xff);
1906                 if (g != 0) {
1907                     buffer_g_row[col] = (g << 8) | (g << 24);
1908                 }
1909             }
1910         }
1911 
1912         parade_buffer_g_dirty = false;
1913     }
1914 
1915     if (parade_buffer_b_dirty && needBlue) {
1916         parade_buffer_b.assign(buffer_size, 0);
1917         assert(parade_buffer_b.size() % 4 == 0);
1918 
1919         for (int val = 0; val < wave_height; val++) {
1920             const int* const b_row = bwave[val];
1921             std::uint32_t* const buffer_b_row = reinterpret_cast<uint32_t*>(parade_buffer_b.data() + (255 - val) * cairo_stride);
1922             for (int col = 0; col < wave_width; col++) {
1923                 const unsigned char b = std::min<float>(scale * b_row[col], 0xff);
1924                 if (b != 0) {
1925                     const unsigned char green = b * rgb_B[1]; // Make blue easier to see.
1926                     buffer_b_row[col] = b | (green << 8) | (b << 24);
1927                 }
1928             }
1929         }
1930 
1931         parade_buffer_b_dirty = false;
1932     }
1933 
1934     if (wave_buffer_luma_dirty && needLuma) {
1935         wave_buffer_luma.assign(buffer_size, 0);
1936         assert(wave_buffer_luma.size() % 4 == 0);
1937 
1938         for (int val = 0; val < wave_height; val++) {
1939             const int* const l_row = lwave[val];
1940             std::uint32_t* const buffer_row =
1941                 reinterpret_cast<uint32_t*>(wave_buffer_luma.data() + (255 - val) * cairo_stride);
1942             for (int col = 0; col < wave_width; col++) {
1943                 const unsigned char l = std::min<float>(scale * l_row[col], 0xff);
1944                 buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24);
1945             }
1946         }
1947 
1948         wave_buffer_luma_dirty = false;
1949     }
1950 
1951     std::vector<unsigned char*> buffers;
1952     if (needLuma) {
1953         buffers.push_back(wave_buffer_luma.data());
1954     }
1955     if (needRed) {
1956         buffers.push_back(parade_buffer_r.data());
1957     }
1958     if (needGreen) {
1959         buffers.push_back(parade_buffer_g.data());
1960     }
1961     if (needBlue) {
1962         buffers.push_back(parade_buffer_b.data());
1963     }
1964 
1965     auto orig_matrix = cr->get_matrix();
1966     const double display_wave_width = static_cast<double>(w) / buffers.size();
1967     for (unsigned i = 0; i < buffers.size(); i++) {
1968         Cairo::RefPtr<Cairo::ImageSurface> surface;
1969         cr->translate(i * display_wave_width, padding);
1970         cr->scale(display_wave_width / wave_width, (h - 2 * padding) / wave_height);
1971         surface = Cairo::ImageSurface::create(
1972             buffers[i], Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride);
1973         cr->set_source(surface, 0, 0);
1974         cr->set_operator(Cairo::OPERATOR_OVER);
1975         cr->paint();
1976         surface->finish();
1977         cr->set_matrix(orig_matrix);
1978     }
1979 }
1980 
drawVectorscope(Cairo::RefPtr<Cairo::Context> & cr,int w,int h)1981 void HistogramArea::drawVectorscope(Cairo::RefPtr<Cairo::Context> &cr, int w, int h)
1982 {
1983     if (scopeType != ScopeType::VECTORSCOPE_HC && scopeType != ScopeType::VECTORSCOPE_HS) {
1984         return;
1985     }
1986 
1987     const auto& vect = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc : vect_hs;
1988     auto& vect_buffer = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer : vect_hs_buffer;
1989     auto& vect_buffer_dirty = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer_dirty : vect_hs_buffer_dirty;
1990 
1991     const int vect_width = vect.width();
1992     const int vect_height = vect.height();
1993     // Arbitrary scale factor multiplied by vectorscope area and divided by
1994     // current scale.
1995     const float scale = trace_brightness * 8.f * vect_width * vect_height / vectorscope_scale;
1996 
1997     // See Cairo documentation on stride.
1998     const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, vect_width);
1999 
2000     if (vect_buffer_dirty && vectorscope_scale > 0) {
2001         if (vect_buffer.size() != static_cast<std::size_t>(cairo_stride) * vect_height) {
2002             vect_buffer.resize(static_cast<std::size_t>(cairo_stride) * vect_height);
2003         }
2004 
2005         assert(vect_buffer.size() % 4 == 0);
2006 
2007         for (int y = 0; y < vect_height; y++) {
2008             const int* const vect_row = vect[y];
2009             std::uint32_t* const buffer_row =
2010                 reinterpret_cast<uint32_t*>(vect_buffer.data() + (vect_height - 1 - y) * cairo_stride);
2011             for (int x = 0; x < vect_width; x++) {
2012                 const unsigned char value = std::min<float>(scale * vect_row[x], 0xff);
2013                 buffer_row[x] = value | (value << 8) | (value << 16) | (value << 24);
2014             }
2015         }
2016 
2017         vect_buffer_dirty = false;
2018     }
2019 
2020     const bool fit_width =
2021         vect_width * (h - 2 * padding) > vect_height * (w - 2 * padding);
2022     const float scope_scale = fit_width ?
2023         (w - 2 * padding) / vect_width : (h - 2 * padding) / vect_height;
2024     const float scope_size = (vectorscope_scale > 0) ?
2025         scope_scale * std::max<double>(vect_width, vect_height) : std::min<float>(w, h) - 2 * padding;
2026     const float o_x = (w - scope_scale * vect_width) / 2;
2027     const float o_y = (h - scope_scale * vect_height) / 2;
2028     const double s = RTScalable::getScale();
2029     auto orig_matrix = cr->get_matrix();
2030     const double line_length = scope_size / 2.0;
2031     std::valarray<double> ch_ds(1);
2032 
2033     cr->translate(w / 2.0, h / 2.0);
2034     cr->set_line_width (1.0 * s);
2035     cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
2036     ch_ds[0] = 4;
2037 
2038     if (scopeType == ScopeType::VECTORSCOPE_HS) { // Hue-Saturation.
2039         // RYGCBM lines.
2040         cr->set_line_width (2.0 * s);
2041         constexpr double color_labels[6][3] = {
2042             {1, 0, 0}, // R
2043             {0, 1, 0}, // G
2044             {0, 0, 1}, // B
2045             {0, 1, 1}, // C
2046             {1, 0, 1}, // M
2047             {1, 1, 0}, // Y
2048         };
2049         for (int i = 0; i < 3; i++) {
2050             auto gradient = Cairo::LinearGradient::create(-line_length, 0, line_length, 0);
2051             const double (&color_1)[3] = color_labels[i];
2052             const double (&color_2)[3] = color_labels[i + 3];
2053             cr->set_source(gradient);
2054             gradient->add_color_stop_rgba(0, color_2[0], color_2[1], color_2[2], 0.5);
2055             gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25);
2056             gradient->add_color_stop_rgba(1, color_1[0], color_1[1], color_1[2], 0.5);
2057             cr->move_to(-line_length, 0);
2058             cr->line_to(line_length, 0);
2059             cr->rotate_degrees(-120);
2060             cr->stroke();
2061         }
2062         cr->set_line_width (1.0 * s);
2063         cr->set_source_rgba (1, 1, 1, 0.25);
2064         // 100% saturation circle.
2065         cr->arc(0, 0, scope_size / 2.0, 0, 2 * RT_PI);
2066         cr->stroke();
2067         // 25%, 50%, and 75% saturation.
2068         cr->set_dash(ch_ds, 0);
2069         for (int i = 1; i < 4; i++) {
2070             cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI);
2071             cr->stroke();
2072         }
2073         // HSV skin tone line derived from -I axis of YIQ.
2074         cr->rotate(-0.134900 * RT_PI);
2075         cr->move_to(0, 0);
2076         cr->line_to(line_length, 0);
2077         cr->stroke();
2078         cr->unset_dash();
2079     } else if (scopeType == ScopeType::VECTORSCOPE_HC) { // Hue-Chroma.
2080         // a and b axes.
2081         Cairo::RefPtr<Cairo::LinearGradient> gradient;
2082         cr->set_line_width (2.0 * s);
2083         gradient = Cairo::LinearGradient::create(0, -line_length, 0, line_length);
2084         cr->set_source(gradient);
2085         gradient->add_color_stop_rgba(0, 1, 1, 0, 0.5); // "yellow"
2086         gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral
2087         gradient->add_color_stop_rgba(1, 0, 0, 1, 0.5); // "blue"
2088         cr->move_to(0, 0);
2089         cr->line_to(0, line_length);
2090         cr->move_to(0, 0);
2091         cr->line_to(0, -line_length);
2092         cr->stroke();
2093         gradient = Cairo::LinearGradient::create(-line_length, 0, line_length, 0);
2094         cr->set_source(gradient);
2095         gradient->add_color_stop_rgba(0, 0, 1, 0, 0.5); // "green"
2096         gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral
2097         gradient->add_color_stop_rgba(1, 1, 0, 1, 0.5); // "magenta"
2098         cr->move_to(0, 0);
2099         cr->line_to(line_length, 0);
2100         cr->move_to(0, 0);
2101         cr->line_to(-line_length, 0);
2102         cr->stroke();
2103         cr->set_source_rgba (1, 1, 1, 0.25);
2104         cr->set_line_width (1.0 * s);
2105         // 25%, 50%, 75%, and 100% of standard chroma range.
2106         cr->set_dash(ch_ds, 0);
2107         for (int i = 1; i <= 4; i++) {
2108             cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI);
2109             cr->stroke();
2110         }
2111         // CIELAB skin tone line, approximated by 50% saturation and
2112         // value along the HSV skin tone line.
2113         cr->rotate(-0.321713 * RT_PI);
2114         cr->move_to(0, 0);
2115         cr->line_to(line_length, 0);
2116         cr->stroke();
2117         cr->unset_dash();
2118     }
2119     cr->set_matrix(orig_matrix);
2120 
2121     // Vectorscope trace.
2122     if (vectorscope_scale > 0) {
2123         Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(
2124             vect_buffer.data(), Cairo::FORMAT_ARGB32, vect_width, vect_height, cairo_stride);
2125         cr->translate(o_x, o_y);
2126         cr->scale(scope_scale, scope_scale);
2127         cr->set_source(surface, 0, 0);
2128         cr->set_operator(Cairo::OPERATOR_OVER);
2129         cr->paint();
2130         surface->finish();
2131         cr->set_matrix(orig_matrix);
2132 
2133         if (needPointer && pointer_red >= 0 && pointer_green >= 0 && pointer_blue >= 0) {
2134             float cx, cy;
2135             if (scopeType == ScopeType::VECTORSCOPE_HS) {
2136                 float H, S, L;
2137                 Color::rgb2hslfloat(pointer_red * 257.f, pointer_green * 257.f, pointer_blue * 257.f, H, S, L);
2138                 cx = (w + scope_size * S * std::cos(H * 2 * RT_PI_F)) / 2;
2139                 cy = (h - scope_size * S * std::sin(H * 2 * RT_PI_F)) / 2;
2140             } else {
2141                 constexpr float ab_factor = 1.f / 256.f;
2142                 cx = w / 2.f + scope_size * pointer_a * ab_factor;
2143                 cy = h / 2.f - scope_size * pointer_b * ab_factor;
2144             }
2145             const float crosshair_size = 20.f * s;
2146             cr->set_source_rgba(1, 1, 1, 0.5);
2147             cr->move_to(cx - crosshair_size, cy);
2148             cr->line_to(cx + crosshair_size, cy);
2149             cr->move_to(cx, cy - crosshair_size);
2150             cr->line_to(cx, cy + crosshair_size);
2151             cr->stroke();
2152             cr->arc(cx, cy, 3 * s, 0, 2 * RT_PI);
2153             cr->set_source_rgb(1, 1, 1);
2154             cr->fill_preserve();
2155             cr->set_source_rgb(0, 0, 0);
2156             cr->set_line_width (1.0 * s);
2157             cr->stroke();
2158         }
2159     }
2160 }
2161 
drawWaveform(Cairo::RefPtr<Cairo::Context> & cr,int w,int h)2162 void HistogramArea::drawWaveform(Cairo::RefPtr<Cairo::Context> &cr, int w, int h)
2163 {
2164     // Arbitrary scale factor divided by current scale.
2165     const float scale = trace_brightness * 32.f * 255.f / waveform_scale;
2166     const int wave_width = rwave.width();
2167     const int wave_height = rwave.height();
2168 
2169     // See Cairo documentation on stride.
2170     const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, rwave.width());
2171     const auto buffer_size = static_cast<std::vector<unsigned char>::size_type>(wave_height) * cairo_stride;
2172 
2173     if (wave_buffer_dirty && (needRed || needGreen || needBlue)) {
2174         wave_buffer.assign(buffer_size, 0);
2175         assert(wave_buffer.size() % 4 == 0);
2176 
2177         for (int val = 0; val < wave_height; val++) {
2178             const int* const r_row = rwave[val];
2179             const int* const g_row = gwave[val];
2180             const int* const b_row = bwave[val];
2181             std::uint32_t* const buffer_row = reinterpret_cast<uint32_t*>(wave_buffer.data() + (255 - val) * cairo_stride);
2182             for (int col = 0; col < wave_width; col++) {
2183                 const unsigned char r = needRed ? std::min<float>(scale * r_row[col], 0xff) : 0;
2184                 const unsigned char b = needBlue ? std::min<float>(scale * b_row[col], 0xff) : 0;
2185                 const unsigned char g = needGreen ? rtengine::LIM(std::min<float>(scale * g_row[col], 0xff) + float(needBlue ? b * rgb_B[1] : 0), 0.f, float(0xff)) : 0;
2186                 const unsigned char value = rtengine::max(r, g, b);
2187                 if (value != 0) {
2188                     // Ensures correct order regardless of endianness.
2189                     buffer_row[col] = b | (g << 8) | (r << 16) | (value << 24);
2190                 }
2191             }
2192         }
2193 
2194         wave_buffer_dirty = false;
2195     }
2196 
2197     if (wave_buffer_luma_dirty && needLuma) {
2198         wave_buffer_luma.assign(buffer_size, 0);
2199         assert(wave_buffer_luma.size() % 4 == 0);
2200 
2201         for (int val = 0; val < wave_height; val++) {
2202             const int* const l_row = lwave[val];
2203             std::uint32_t* const buffer_row =
2204                 reinterpret_cast<uint32_t*>(wave_buffer_luma.data() + (255 - val) * cairo_stride);
2205             for (int col = 0; col < wave_width; col++) {
2206                 const unsigned char l = std::min<float>(scale * l_row[col], 0xff);
2207                 buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24);
2208             }
2209         }
2210 
2211         wave_buffer_luma_dirty = false;
2212     }
2213 
2214     Cairo::RefPtr<Cairo::ImageSurface> surface;
2215     auto orig_matrix = cr->get_matrix();
2216     cr->translate(0, padding);
2217     cr->scale(static_cast<double>(w) / wave_width, (h - 2 * padding) / wave_height);
2218     if (needLuma) {
2219         surface = Cairo::ImageSurface::create(
2220             wave_buffer_luma.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride);
2221         cr->set_source(surface, 0, 0);
2222         cr->set_operator(Cairo::OPERATOR_OVER);
2223         cr->paint();
2224         surface->finish();
2225     }
2226     if (needRed || needGreen || needBlue) {
2227         surface = Cairo::ImageSurface::create(
2228             wave_buffer.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride);
2229         cr->set_source(surface, 0, 0);
2230         cr->set_operator(Cairo::OPERATOR_OVER);
2231         cr->paint();
2232         surface->finish();
2233     }
2234     cr->set_matrix(orig_matrix);
2235 }
2236 
on_draw(const::Cairo::RefPtr<Cairo::Context> & cr)2237 bool HistogramArea::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
2238 {
2239 
2240     if (!updatePending() && (get_width() != oldwidth || get_height() != oldheight || isDirty())) {
2241         updateBackBuffer ();
2242     }
2243 
2244     const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
2245     style->render_background(cr, 0, 0, get_width(), get_height());
2246     copySurface(cr, NULL);
2247     style->render_frame (cr, 0, 0, get_width(), get_height());
2248 
2249     return true;
2250 }
2251 
on_button_press_event(GdkEventButton * event)2252 bool HistogramArea::on_button_press_event (GdkEventButton* event)
2253 {
2254     if (!is_main_) {
2255         return true;
2256     }
2257 
2258     isPressed = true;
2259     movingPosition = event->x;
2260 
2261     if (event->type == GDK_2BUTTON_PRESS && event->button == 1
2262         && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)
2263     ) {
2264 
2265         drawMode = (drawMode + 1) % 3;
2266         options.histogramDrawMode = (options.histogramDrawMode + 1) % 3;
2267 
2268         if (myDrawModeListener) {
2269             myDrawModeListener->toggleButtonMode ();
2270         }
2271 
2272         updateBackBuffer ();
2273         queue_draw ();
2274     }
2275 
2276     return true;
2277 }
2278 
on_button_release_event(GdkEventButton * event)2279 bool HistogramArea::on_button_release_event (GdkEventButton* event)
2280 {
2281     if (!is_main_) {
2282         return true;
2283     }
2284 
2285     isPressed = false;
2286     return true;
2287 }
2288 
on_motion_notify_event(GdkEventMotion * event)2289 bool HistogramArea::on_motion_notify_event (GdkEventMotion* event)
2290 {
2291     if (!is_main_) {
2292         return true;
2293     }
2294 
2295     if (
2296         drawMode == 0
2297         && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)
2298     ) {
2299         return false;
2300     }
2301 
2302     if (!isPressed) {
2303         return true;
2304     }
2305 
2306     if (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW) { // Adjust log scale.
2307         double mod = 1 + (event->x - movingPosition) / get_width();
2308 
2309         factor /= mod;
2310         if (factor < 1.0)
2311             factor = 1.0;
2312         if (factor > 100.0)
2313             factor = 100.0;
2314 
2315         sigFactorChanged.emit(factor);
2316 
2317         setDirty(true);
2318         queue_draw ();
2319     } else if (
2320         scopeType == ScopeType::PARADE
2321         || scopeType == ScopeType::WAVEFORM
2322         || scopeType == ScopeType::VECTORSCOPE_HC
2323         || scopeType == ScopeType::VECTORSCOPE_HS
2324     ) { // Adjust brightness.
2325         constexpr float RANGE = MAX_BRIGHT / MIN_BRIGHT;
2326         double dx = (event->x - movingPosition) / get_width();
2327         float new_brightness = LIM<float>(trace_brightness * pow(RANGE, dx), MIN_BRIGHT, MAX_BRIGHT);
2328         setBrightness(new_brightness);
2329         movingPosition = event->x;
2330     }
2331 
2332     return true;
2333 }
2334 
getBrightness(void)2335 float HistogramArea::getBrightness(void)
2336 {
2337     return trace_brightness;
2338 }
2339 
setBrightness(float brightness)2340 void HistogramArea::setBrightness(float brightness)
2341 {
2342     brightness = LIM<float>(brightness, MIN_BRIGHT, MAX_BRIGHT);
2343     if (brightness != trace_brightness) {
2344         parade_buffer_r_dirty = parade_buffer_g_dirty = parade_buffer_b_dirty = wave_buffer_dirty = wave_buffer_luma_dirty = vect_hc_buffer_dirty = vect_hs_buffer_dirty = true;
2345         trace_brightness = brightness;
2346         setDirty(true);
2347         queue_draw();
2348 
2349         signal_brightness_changed.emit(trace_brightness);
2350     }
2351 }
2352 
getBrighnessChangedSignal(void)2353 HistogramArea::SignalBrightnessChanged HistogramArea::getBrighnessChangedSignal(void)
2354 {
2355     return signal_brightness_changed;
2356 }
2357 
signal_factor_changed()2358 HistogramArea::type_signal_factor_changed HistogramArea::signal_factor_changed()
2359 {
2360     return sigFactorChanged;
2361 }
2362