Previously, changing to a different scope was done by clicking the scope type button and cycling through all scopes. This commit decreases the number of clicks needed to switch between two scopes by adding a radio selector. The radio buttons can be shown or hidden by pressing the scope type button. This commit also makes the raw histogram one of the scope types.
1858 lines
63 KiB
C++
1858 lines
63 KiB
C++
/*
|
|
* This file is part of RawTherapee.
|
|
*
|
|
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
|
*
|
|
* RawTherapee is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* RawTherapee is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with RawTherapee. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "histogrampanel.h"
|
|
#include "multilangmgr.h"
|
|
#include "guiutils.h"
|
|
#include "options.h"
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include "../rtengine/array2D.h"
|
|
#include "../rtengine/LUT.h"
|
|
#include "rtimage.h"
|
|
#include "../rtengine/color.h"
|
|
|
|
using namespace rtengine;
|
|
|
|
|
|
//
|
|
//
|
|
// HistogramPanel
|
|
HistogramPanel::HistogramPanel () :
|
|
pointer_moved_delayed_call(
|
|
[this](bool validPos, const Glib::ustring &profile, const Glib::ustring &profileW, int r, int g, int b)
|
|
{
|
|
bool update_hist_area;
|
|
|
|
if (!validPos) {
|
|
// do something to un-show vertical bars
|
|
if (histogramRGBArea) {
|
|
histogramRGBArea->updateBackBuffer(-1, -1, -1);
|
|
}
|
|
update_hist_area = histogramArea->updatePointer(-1, -1, -1);
|
|
} else {
|
|
// do something to show vertical bars
|
|
if (histogramRGBArea) {
|
|
histogramRGBArea->updateBackBuffer(r, g, b, profile, profileW);
|
|
}
|
|
update_hist_area = histogramArea->updatePointer(r, g, b, profile, profileW);
|
|
}
|
|
if (histogramRGBArea) {
|
|
histogramRGBArea->queue_draw();
|
|
}
|
|
if (update_hist_area) {
|
|
histogramArea->queue_draw();
|
|
}
|
|
},
|
|
50,
|
|
100
|
|
),
|
|
panel_listener(nullptr)
|
|
{
|
|
setExpandAlignProperties(this, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
|
|
set_name("HistogramPanel");
|
|
|
|
histogramArea = Gtk::manage (new HistogramArea (this));
|
|
setExpandAlignProperties(histogramArea, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
|
|
|
|
histogramRGBAreaHori.reset(new HistogramRGBAreaHori());
|
|
setExpandAlignProperties(histogramRGBAreaHori.get(), true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_END);
|
|
histogramRGBAreaHori->show();
|
|
|
|
histogramRGBAreaVert.reset(new HistogramRGBAreaVert());
|
|
setExpandAlignProperties(histogramRGBAreaVert.get(), false, true, Gtk::ALIGN_END, Gtk::ALIGN_FILL);
|
|
histogramRGBAreaVert->show();
|
|
|
|
switch (options.histogramScopeType) {
|
|
case ScopeType::NONE:
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
histogramRGBArea = nullptr;
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
histogramRGBArea = histogramRGBAreaVert.get();
|
|
break;
|
|
case ScopeType::HISTOGRAM:
|
|
histogramRGBArea = histogramRGBAreaHori.get();
|
|
break;
|
|
}
|
|
|
|
// connecting the two childs
|
|
histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaHori, &HistogramRGBArea::factorChanged) );
|
|
histogramArea->signal_factor_changed().connect( sigc::mem_fun(*histogramRGBAreaVert, &HistogramRGBArea::factorChanged) );
|
|
|
|
gfxGrid = Gtk::manage (new Gtk::Grid ());
|
|
gfxGrid->set_row_spacing(1);
|
|
gfxGrid->set_column_spacing(1);
|
|
histogramRGBAreaHori->setParent(gfxGrid);
|
|
histogramRGBAreaVert->setParent(gfxGrid);
|
|
gfxGrid->add(*histogramArea);
|
|
|
|
if (options.histogramBar) {
|
|
showRGBBar();
|
|
}
|
|
|
|
redImage = new RTImage ("histogram-red-on-small.png");
|
|
greenImage = new RTImage ("histogram-green-on-small.png");
|
|
blueImage = new RTImage ("histogram-blue-on-small.png");
|
|
valueImage = new RTImage ("histogram-silver-on-small.png");
|
|
chroImage = new RTImage ("histogram-gold-on-small.png");
|
|
barImage = new RTImage ("histogram-bar-on-small.png");
|
|
|
|
redImage_g = new RTImage ("histogram-red-off-small.png");
|
|
greenImage_g = new RTImage ("histogram-green-off-small.png");
|
|
blueImage_g = new RTImage ("histogram-blue-off-small.png");
|
|
valueImage_g = new RTImage ("histogram-silver-off-small.png");
|
|
chroImage_g = new RTImage ("histogram-gold-off-small.png");
|
|
barImage_g = new RTImage ("histogram-bar-off-small.png");
|
|
|
|
mode0Image = new RTImage ("histogram-mode-linear-small.png");
|
|
mode1Image = new RTImage ("histogram-mode-logx-small.png");
|
|
mode2Image = new RTImage ("histogram-mode-logxy-small.png");
|
|
|
|
histImage.reset(new RTImage("histogram-type-histogram-small.png"));
|
|
histRawImage.reset(new RTImage("histogram-bayer-on-small.png"));
|
|
waveImage.reset(new RTImage("histogram-type-waveform-small.png"));
|
|
vectHcImage.reset(new RTImage("histogram-type-vectorscope-hc-small.png"));
|
|
vectHsImage.reset(new RTImage("histogram-type-vectorscope-hs-small.png"));
|
|
|
|
histImageOn.reset(new RTImage("histogram-type-histogram-small.png"));
|
|
histRawImageOn.reset(new RTImage("histogram-bayer-on-small.png"));
|
|
waveImageOn.reset(new RTImage("histogram-type-waveform-small.png"));
|
|
vectHcImageOn.reset(new RTImage("histogram-type-vectorscope-hc-small.png"));
|
|
vectHsImageOn.reset(new RTImage("histogram-type-vectorscope-hs-small.png"));
|
|
histImageOff.reset(new RTImage("histogram-type-histogram-off-small.png"));
|
|
histRawImageOff.reset(new RTImage("histogram-bayer-off-small.png"));
|
|
waveImageOff.reset(new RTImage("histogram-type-waveform-off-small.png"));
|
|
vectHcImageOff.reset(new RTImage("histogram-type-vectorscope-hc-off-small.png"));
|
|
vectHsImageOff.reset(new RTImage("histogram-type-vectorscope-hs-off-small.png"));
|
|
|
|
showRed = Gtk::manage (new Gtk::ToggleButton ());
|
|
showGreen = Gtk::manage (new Gtk::ToggleButton ());
|
|
showBlue = Gtk::manage (new Gtk::ToggleButton ());
|
|
showValue = Gtk::manage (new Gtk::ToggleButton ());
|
|
showChro = Gtk::manage (new Gtk::ToggleButton ());
|
|
showMode = Gtk::manage (new Gtk::Button ());
|
|
scopeType = Gtk::manage (new Gtk::ToggleButton ());
|
|
showBAR = Gtk::manage (new Gtk::ToggleButton ());
|
|
|
|
Gtk::RadioButtonGroup scopeTypeGroup;
|
|
scopeHistBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
|
|
scopeHistRawBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
|
|
scopeWaveBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
|
|
scopeVectHcBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
|
|
scopeVectHsBtn = Gtk::manage(new Gtk::RadioButton(scopeTypeGroup));
|
|
scopeHistBtn->set_mode(false);
|
|
scopeHistRawBtn->set_mode(false);
|
|
scopeWaveBtn->set_mode(false);
|
|
scopeVectHcBtn->set_mode(false);
|
|
scopeVectHsBtn->set_mode(false);
|
|
|
|
showRed->set_name("histButton");
|
|
showRed->set_can_focus(false);
|
|
showGreen->set_name("histButton");
|
|
showGreen->set_can_focus(false);
|
|
showBlue->set_name("histButton");
|
|
showBlue->set_can_focus(false);
|
|
showValue->set_name("histButton");
|
|
showValue->set_can_focus(false);
|
|
showChro->set_name("histButton");
|
|
showChro->set_can_focus(false);
|
|
showMode->set_name("histButton");
|
|
showMode->set_can_focus(false);
|
|
scopeType->set_name("histButton");
|
|
scopeType->set_can_focus(false);
|
|
showBAR->set_name("histButton");
|
|
showBAR->set_can_focus(false);
|
|
scopeHistBtn->set_name("histButton");
|
|
scopeHistBtn->set_can_focus(false);
|
|
scopeHistRawBtn->set_name("histButton");
|
|
scopeHistRawBtn->set_can_focus(false);
|
|
scopeWaveBtn->set_name("histButton");
|
|
scopeWaveBtn->set_can_focus(false);
|
|
scopeVectHcBtn->set_name("histButton");
|
|
scopeVectHcBtn->set_can_focus(false);
|
|
scopeVectHsBtn->set_name("histButton");
|
|
scopeVectHsBtn->set_can_focus(false);
|
|
|
|
showRed->set_relief (Gtk::RELIEF_NONE);
|
|
showGreen->set_relief (Gtk::RELIEF_NONE);
|
|
showBlue->set_relief (Gtk::RELIEF_NONE);
|
|
showValue->set_relief (Gtk::RELIEF_NONE);
|
|
showChro->set_relief (Gtk::RELIEF_NONE);
|
|
showMode->set_relief (Gtk::RELIEF_NONE);
|
|
scopeType->set_relief (Gtk::RELIEF_NONE);
|
|
showBAR->set_relief (Gtk::RELIEF_NONE);
|
|
scopeHistBtn->set_relief (Gtk::RELIEF_NONE);
|
|
scopeHistRawBtn->set_relief (Gtk::RELIEF_NONE);
|
|
scopeWaveBtn->set_relief (Gtk::RELIEF_NONE);
|
|
scopeVectHcBtn->set_relief (Gtk::RELIEF_NONE);
|
|
scopeVectHsBtn->set_relief (Gtk::RELIEF_NONE);
|
|
|
|
showRed->set_tooltip_text (M("HISTOGRAM_TOOLTIP_R"));
|
|
showGreen->set_tooltip_text (M("HISTOGRAM_TOOLTIP_G"));
|
|
showBlue->set_tooltip_text (M("HISTOGRAM_TOOLTIP_B"));
|
|
showValue->set_tooltip_text (M("HISTOGRAM_TOOLTIP_L"));
|
|
showChro->set_tooltip_text (M("HISTOGRAM_TOOLTIP_CHRO"));
|
|
showMode->set_tooltip_text (M("HISTOGRAM_TOOLTIP_MODE"));
|
|
scopeType->set_tooltip_text (M("HISTOGRAM_TOOLTIP_TYPE"));
|
|
showBAR->set_tooltip_text (M("HISTOGRAM_TOOLTIP_BAR"));
|
|
scopeHistBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM"));
|
|
scopeHistRawBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_HISTOGRAM_RAW"));
|
|
scopeWaveBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_WAVEFORM"));
|
|
scopeVectHcBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HC"));
|
|
scopeVectHsBtn->set_tooltip_text(M("HISTOGRAM_TOOLTIP_TYPE_VECTORSCOPE_HS"));
|
|
|
|
buttonGrid = Gtk::manage (new Gtk::Grid ());
|
|
buttonGrid->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
|
|
persistentButtons = Gtk::manage(new Gtk::Box());
|
|
persistentButtons->set_orientation(Gtk::ORIENTATION_VERTICAL);
|
|
scopeButtons = Gtk::manage(new Gtk::Box());
|
|
scopeButtons->set_orientation(Gtk::ORIENTATION_VERTICAL);
|
|
|
|
showRed->set_active (options.histogramRed);
|
|
showGreen->set_active (options.histogramGreen);
|
|
showBlue->set_active (options.histogramBlue);
|
|
showValue->set_active (options.histogramLuma);
|
|
showChro->set_active (options.histogramChroma);
|
|
// no showMode->set_active(), as it's not a ToggleButton
|
|
scopeType->set_active(options.histogramShowScopeButtons);
|
|
showBAR->set_active (options.histogramBar);
|
|
|
|
showRed->set_image (showRed->get_active() ? *redImage : *redImage_g);
|
|
showGreen->set_image (showGreen->get_active() ? *greenImage : *greenImage_g);
|
|
showBlue->set_image (showBlue->get_active() ? *blueImage : *blueImage_g);
|
|
showValue->set_image (showValue->get_active() ? *valueImage : *valueImage_g);
|
|
showChro->set_image (showChro->get_active() ? *chroImage : *chroImage_g);
|
|
if (options.histogramDrawMode == 0)
|
|
showMode->set_image(*mode0Image);
|
|
else if (options.histogramDrawMode == 1)
|
|
showMode->set_image(*mode1Image);
|
|
else
|
|
showMode->set_image(*mode2Image);
|
|
scopeHistBtn->set_image(*histImageOff);
|
|
scopeHistRawBtn->set_image(*histRawImageOff);
|
|
scopeWaveBtn->set_image(*waveImageOff);
|
|
scopeVectHcBtn->set_image(*vectHcImageOff);
|
|
scopeVectHsBtn->set_image(*vectHsImageOff);
|
|
switch(options.histogramScopeType) {
|
|
case ScopeType::HISTOGRAM:
|
|
scopeHistBtn->set_active();
|
|
scopeHistBtn->set_image(*histImageOn);
|
|
break;
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
scopeHistRawBtn->set_active();
|
|
scopeHistRawBtn->set_image(*histRawImageOn);
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
scopeWaveBtn->set_active();
|
|
scopeWaveBtn->set_image(*waveImageOn);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
scopeVectHsBtn->set_active();
|
|
scopeVectHsBtn->set_image(*vectHsImageOn);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
scopeVectHcBtn->set_active();
|
|
scopeVectHcBtn->set_image(*vectHcImageOn);
|
|
break;
|
|
case ScopeType::NONE:
|
|
break;
|
|
}
|
|
showBAR->set_image (showBAR->get_active() ? *barImage : *barImage_g);
|
|
|
|
type_changed();
|
|
|
|
setExpandAlignProperties(showRed , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showGreen, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showBlue , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showValue, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showChro , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showMode , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(scopeType, false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
setExpandAlignProperties(showBAR , false, false, Gtk::ALIGN_START, Gtk::ALIGN_CENTER);
|
|
|
|
showRed->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::red_toggled), showRed );
|
|
showGreen->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::green_toggled), showGreen );
|
|
showBlue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::blue_toggled), showBlue );
|
|
showValue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::value_toggled), showValue );
|
|
showChro->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::chro_toggled), showChro );
|
|
showMode->signal_released().connect( sigc::mem_fun(*this, &HistogramPanel::mode_released), showMode );
|
|
scopeType->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::type_pressed) );
|
|
showBAR->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::bar_toggled), showBAR );
|
|
scopeHistBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistBtn));
|
|
scopeHistRawBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeHistRawBtn));
|
|
scopeWaveBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeWaveBtn));
|
|
scopeVectHcBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHcBtn));
|
|
scopeVectHsBtn->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &HistogramPanel::type_selected), scopeVectHsBtn));
|
|
|
|
persistentButtons->add(*showRed);
|
|
persistentButtons->add(*showGreen);
|
|
persistentButtons->add(*showBlue);
|
|
persistentButtons->add(*showValue);
|
|
persistentButtons->add(*showChro);
|
|
persistentButtons->add(*showMode);
|
|
persistentButtons->add(*scopeType);
|
|
persistentButtons->add(*showBAR);
|
|
|
|
scopeButtons->add(*scopeHistBtn);
|
|
scopeButtons->add(*scopeHistRawBtn);
|
|
scopeButtons->add(*scopeWaveBtn);
|
|
scopeButtons->add(*scopeVectHsBtn);
|
|
scopeButtons->add(*scopeVectHcBtn);
|
|
|
|
// Put the button vbox next to the window's border to be less disturbing
|
|
if (options.histogramPosition == 1) {
|
|
buttonGrid->add(*persistentButtons);
|
|
buttonGrid->add(*scopeButtons);
|
|
|
|
add (*buttonGrid);
|
|
add (*gfxGrid);
|
|
} else {
|
|
buttonGrid->add(*scopeButtons);
|
|
buttonGrid->add(*persistentButtons);
|
|
|
|
add (*gfxGrid);
|
|
add (*buttonGrid);
|
|
}
|
|
|
|
show_all ();
|
|
scopeButtons->set_no_show_all();
|
|
scopeButtons->set_visible(options.histogramShowScopeButtons);
|
|
|
|
rconn = signal_size_allocate().connect( sigc::mem_fun(*this, &HistogramPanel::resized) );
|
|
}
|
|
|
|
HistogramPanel::~HistogramPanel ()
|
|
{
|
|
pointer_moved_delayed_call.cancel();
|
|
|
|
delete redImage;
|
|
delete greenImage;
|
|
delete blueImage;
|
|
delete valueImage;
|
|
delete chroImage;
|
|
delete mode0Image;
|
|
delete mode1Image;
|
|
delete mode2Image;
|
|
delete barImage;
|
|
|
|
delete redImage_g;
|
|
delete greenImage_g;
|
|
delete blueImage_g;
|
|
delete valueImage_g;
|
|
delete chroImage_g;
|
|
delete barImage_g;
|
|
|
|
}
|
|
|
|
void HistogramPanel::showRGBBar()
|
|
{
|
|
Gtk::PositionType pos;
|
|
|
|
if (histogramRGBArea == histogramRGBAreaHori.get()) {
|
|
pos = Gtk::POS_BOTTOM;
|
|
} else if (histogramRGBArea == nullptr) {
|
|
return;
|
|
} else {
|
|
if (options.histogramPosition == 1) {
|
|
pos = Gtk::POS_RIGHT;
|
|
} else {
|
|
pos = Gtk::POS_LEFT;
|
|
}
|
|
}
|
|
|
|
gfxGrid->attach_next_to(*histogramRGBArea, *histogramArea, pos);
|
|
setHistRGBInvalid();
|
|
histogramRGBArea->setShow(
|
|
options.histogramScopeType == ScopeType::HISTOGRAM
|
|
|| options.histogramScopeType == ScopeType::WAVEFORM
|
|
);
|
|
}
|
|
|
|
void HistogramPanel::resized (Gtk::Allocation& req)
|
|
{
|
|
|
|
if (!histogramArea->updatePending()) {
|
|
histogramArea->updateBackBuffer ();
|
|
histogramArea->queue_draw ();
|
|
}
|
|
|
|
// set histogramRGBArea invalid;
|
|
if (histogramRGBArea) {
|
|
histogramRGBArea->updateBackBuffer(-1, -1, -1);
|
|
histogramRGBArea->queue_draw ();
|
|
}
|
|
|
|
// Store current height of the histogram
|
|
options.histogramHeight = get_height();
|
|
|
|
}
|
|
|
|
void HistogramPanel::red_toggled ()
|
|
{
|
|
showRed->set_image(showRed->get_active() ? *redImage : *redImage_g);
|
|
rgbv_toggled();
|
|
}
|
|
void HistogramPanel::green_toggled ()
|
|
{
|
|
showGreen->set_image(showGreen->get_active() ? *greenImage : *greenImage_g);
|
|
rgbv_toggled();
|
|
}
|
|
void HistogramPanel::blue_toggled ()
|
|
{
|
|
showBlue->set_image(showBlue->get_active() ? *blueImage : *blueImage_g);
|
|
rgbv_toggled();
|
|
}
|
|
void HistogramPanel::value_toggled ()
|
|
{
|
|
removeIfThere(showValue, valueImage, false);
|
|
removeIfThere(showValue, valueImage_g, false);
|
|
showValue->set_image(showValue->get_active() ? *valueImage : *valueImage_g);
|
|
rgbv_toggled();
|
|
}
|
|
void HistogramPanel::chro_toggled ()
|
|
{
|
|
removeIfThere(showChro, chroImage, false);
|
|
removeIfThere(showChro, chroImage_g, false);
|
|
showChro->set_image(showChro->get_active() ? *chroImage : *chroImage_g);
|
|
rgbv_toggled();
|
|
}
|
|
|
|
void HistogramPanel::mode_released ()
|
|
{
|
|
options.histogramDrawMode = (options.histogramDrawMode + 1) % 3;
|
|
if (options.histogramDrawMode == 0)
|
|
showMode->set_image(*mode0Image);
|
|
else if (options.histogramDrawMode == 1)
|
|
showMode->set_image(*mode1Image);
|
|
else
|
|
showMode->set_image(*mode2Image);
|
|
rgbv_toggled();
|
|
}
|
|
|
|
void HistogramPanel::type_pressed()
|
|
{
|
|
options.histogramShowScopeButtons = scopeType->get_active();
|
|
scopeButtons->set_visible(scopeType->get_active());
|
|
}
|
|
|
|
void HistogramPanel::type_selected(Gtk::RadioButton* button)
|
|
{
|
|
ScopeType new_type = ScopeType::NONE;
|
|
|
|
if (button == scopeHistBtn) {
|
|
new_type = ScopeType::HISTOGRAM;
|
|
} else if (button == scopeHistRawBtn) {
|
|
new_type = ScopeType::HISTOGRAM_RAW;
|
|
} else if (button == scopeWaveBtn) {
|
|
new_type = ScopeType::WAVEFORM;
|
|
} else if (button == scopeVectHcBtn) {
|
|
new_type = ScopeType::VECTORSCOPE_HC;
|
|
} else if (button == scopeVectHsBtn) {
|
|
new_type = ScopeType::VECTORSCOPE_HS;
|
|
}
|
|
|
|
if (new_type == options.histogramScopeType) {
|
|
return;
|
|
}
|
|
|
|
switch (options.histogramScopeType) {
|
|
case ScopeType::HISTOGRAM:
|
|
scopeHistBtn->set_image(*histImageOff);
|
|
break;
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
scopeHistRawBtn->set_image(*histRawImageOff);
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
scopeWaveBtn->set_image(*waveImageOff);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
scopeVectHcBtn->set_image(*vectHcImageOff);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
scopeVectHsBtn->set_image(*vectHsImageOff);
|
|
break;
|
|
case ScopeType::NONE:
|
|
break;
|
|
}
|
|
|
|
switch (new_type) {
|
|
case ScopeType::HISTOGRAM:
|
|
scopeHistBtn->set_image(*histImageOn);
|
|
break;
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
scopeHistRawBtn->set_image(*histRawImageOn);
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
scopeWaveBtn->set_image(*waveImageOn);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
scopeVectHcBtn->set_image(*vectHcImageOn);
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
scopeVectHsBtn->set_image(*vectHsImageOn);
|
|
break;
|
|
case ScopeType::NONE:
|
|
break;
|
|
}
|
|
|
|
options.histogramScopeType = new_type;
|
|
|
|
type_changed();
|
|
updateHistAreaOptions();
|
|
if (histogramRGBArea) {
|
|
updateHistRGBAreaOptions();
|
|
}
|
|
}
|
|
|
|
void HistogramPanel::type_changed()
|
|
{
|
|
if (showBAR->get_active() && histogramRGBArea) {
|
|
histogramRGBArea->setShow(false);
|
|
gfxGrid->remove(*histogramRGBArea);
|
|
}
|
|
|
|
switch (options.histogramScopeType) {
|
|
case ScopeType::HISTOGRAM:
|
|
showRed->set_sensitive();
|
|
showGreen->set_sensitive();
|
|
showBlue->set_sensitive();
|
|
showValue->set_sensitive();
|
|
showChro->set_sensitive();
|
|
showMode->set_sensitive();
|
|
scopeType->set_image(*histImage);
|
|
histogramRGBArea = histogramRGBAreaHori.get();
|
|
break;
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
showRed->set_sensitive();
|
|
showGreen->set_sensitive();
|
|
showBlue->set_sensitive();
|
|
showValue->set_sensitive(false);
|
|
showChro->set_sensitive(false);
|
|
showMode->set_sensitive();
|
|
scopeType->set_image(*histRawImage);
|
|
histogramRGBArea = nullptr;
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
showRed->set_sensitive();
|
|
showGreen->set_sensitive();
|
|
showBlue->set_sensitive();
|
|
showValue->set_sensitive();
|
|
showChro->set_sensitive(false);
|
|
showMode->set_sensitive(false);
|
|
scopeType->set_image(*waveImage);
|
|
histogramRGBArea = histogramRGBAreaVert.get();
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
showRed->set_sensitive(false);
|
|
showGreen->set_sensitive(false);
|
|
showBlue->set_sensitive(false);
|
|
showValue->set_sensitive(false);
|
|
showChro->set_sensitive(false);
|
|
showMode->set_sensitive(false);
|
|
if (options.histogramScopeType == ScopeType::VECTORSCOPE_HC) {
|
|
scopeType->set_image(*vectHcImage);
|
|
} else {
|
|
scopeType->set_image(*vectHsImage);
|
|
}
|
|
histogramRGBArea = nullptr;
|
|
break;
|
|
case ScopeType::NONE:
|
|
break;
|
|
}
|
|
|
|
if (panel_listener) {
|
|
updateHistAreaOptions();
|
|
panel_listener->scopeTypeChanged(options.histogramScopeType);
|
|
}
|
|
|
|
if (showBAR->get_active()) {
|
|
showRGBBar();
|
|
}
|
|
}
|
|
|
|
void HistogramPanel::bar_toggled ()
|
|
{
|
|
showBAR->set_image(showBAR->get_active() ? *barImage : *barImage_g);
|
|
rgbv_toggled();
|
|
|
|
if (showBAR->get_active()) {
|
|
showRGBBar();
|
|
} else if (histogramRGBArea) {
|
|
gfxGrid->remove(*histogramRGBArea);
|
|
}
|
|
}
|
|
|
|
void HistogramPanel::rgbv_toggled ()
|
|
{
|
|
// Update Display
|
|
updateHistAreaOptions();
|
|
histogramArea->updateBackBuffer ();
|
|
histogramArea->queue_draw ();
|
|
|
|
if (histogramRGBArea) {
|
|
updateHistRGBAreaOptions();
|
|
histogramRGBArea->updateBackBuffer (0, 0, 0);
|
|
histogramRGBArea->queue_draw ();
|
|
}
|
|
}
|
|
|
|
void HistogramPanel::setHistRGBInvalid ()
|
|
{
|
|
// do something to un-show vertical bars
|
|
histogramRGBArea->updateBackBuffer(-1, -1, -1);
|
|
histogramRGBArea->queue_draw ();
|
|
}
|
|
|
|
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)
|
|
{
|
|
pointer_moved_delayed_call(validPos, profile, profileW, r, g, b);
|
|
}
|
|
|
|
/*
|
|
* Move the vertical button bar to the right side
|
|
* only allowed values for align are Gtk::POS_LEFT and Gtk::POS_RIGHT
|
|
*/
|
|
void HistogramPanel::reorder (Gtk::PositionType align)
|
|
{
|
|
if (align == Gtk::POS_LEFT) {
|
|
gfxGrid->reference();
|
|
removeIfThere(this, gfxGrid, false);
|
|
add (*gfxGrid);
|
|
gfxGrid->unreference();
|
|
|
|
if (histogramRGBArea == histogramRGBAreaVert.get()) {
|
|
gfxGrid->remove(*histogramRGBArea);
|
|
gfxGrid->add(*histogramRGBArea);
|
|
}
|
|
|
|
scopeButtons->reference();
|
|
removeIfThere(buttonGrid, scopeButtons, false);
|
|
buttonGrid->add(*scopeButtons);
|
|
scopeButtons->unreference();
|
|
} else {
|
|
buttonGrid->reference();
|
|
removeIfThere(this, buttonGrid, false);
|
|
add (*buttonGrid);
|
|
buttonGrid->unreference();
|
|
|
|
if (histogramRGBArea == histogramRGBAreaVert.get()) {
|
|
gfxGrid->remove(*histogramArea);
|
|
gfxGrid->add(*histogramArea);
|
|
}
|
|
|
|
persistentButtons->reference();
|
|
removeIfThere(buttonGrid, persistentButtons, false);
|
|
buttonGrid->add(*persistentButtons);
|
|
persistentButtons->unreference();
|
|
}
|
|
}
|
|
|
|
// DrawModeListener interface:
|
|
void HistogramPanel::toggleButtonMode ()
|
|
{
|
|
if (options.histogramDrawMode == 0)
|
|
showMode->set_image(*mode0Image);
|
|
else if (options.histogramDrawMode == 1)
|
|
showMode->set_image(*mode1Image);
|
|
else
|
|
showMode->set_image(*mode2Image);
|
|
}
|
|
|
|
void HistogramPanel::setPanelListener(HistogramPanelListener* listener)
|
|
{
|
|
panel_listener = listener;
|
|
|
|
if (listener) {
|
|
listener->scopeTypeChanged(options.histogramScopeType);
|
|
}
|
|
}
|
|
|
|
void HistogramPanel::updateHistAreaOptions()
|
|
{
|
|
histogramArea->updateOptions(
|
|
showRed->get_active(),
|
|
showGreen->get_active(),
|
|
showBlue->get_active(),
|
|
showValue->get_active(),
|
|
showChro->get_active(),
|
|
options.histogramDrawMode,
|
|
options.histogramScopeType,
|
|
showBAR->get_active()
|
|
);
|
|
}
|
|
|
|
void HistogramPanel::updateHistRGBAreaOptions()
|
|
{
|
|
histogramRGBArea->updateOptions(
|
|
showRed->get_active(),
|
|
showGreen->get_active(),
|
|
showBlue->get_active(),
|
|
showValue->get_active(),
|
|
showChro->get_active(),
|
|
showBAR->get_active()
|
|
);
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
// HistogramScaling
|
|
double HistogramScaling::log(double vsize, double val)
|
|
{
|
|
//double factor = 10.0; // can be tuned if necessary - higher is flatter curve
|
|
return vsize * std::log(factor / (factor + val)) / std::log(factor / (factor + vsize));
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
// HistogramRGBArea
|
|
HistogramRGBArea::HistogramRGBArea () :
|
|
val(0), r(0), g(0), b(0), valid(false),
|
|
needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue),
|
|
needLuma(options.histogramLuma), needChroma(options.histogramChroma),
|
|
showMode(options.histogramBar), barDisplayed(options.histogramBar), parent(nullptr)
|
|
{
|
|
get_style_context()->add_class("drawingarea");
|
|
set_name("HistogramRGBArea");
|
|
|
|
harih = new HistogramRGBAreaIdleHelper;
|
|
harih->harea = this;
|
|
harih->destroyed = false;
|
|
harih->pending = 0;
|
|
}
|
|
|
|
HistogramRGBArea::~HistogramRGBArea ()
|
|
{
|
|
idle_register.destroy();
|
|
|
|
if (harih->pending) {
|
|
harih->destroyed = true;
|
|
} else {
|
|
delete harih;
|
|
}
|
|
}
|
|
|
|
|
|
void HistogramRGBArea::getPreferredThickness(int& min_thickness, int& natural_thickness) const
|
|
{
|
|
int minimumLength = 0;
|
|
int naturalLength = 0;
|
|
getPreferredLength(minimumLength, naturalLength);
|
|
getPreferredThicknessForLength(minimumLength, min_thickness, natural_thickness);
|
|
}
|
|
|
|
void HistogramRGBArea::getPreferredLength(int& min_length, int& natural_length) const
|
|
{
|
|
int s = RTScalable::getScale();
|
|
min_length = 60 * s;
|
|
natural_length = 200 * s;
|
|
}
|
|
|
|
void HistogramRGBArea::getPreferredThicknessForLength(int length, int& min_thickness, int& natural_thickness) const
|
|
{
|
|
int bThickness = length / 30;
|
|
|
|
int s = RTScalable::getScale();
|
|
|
|
if (bThickness > (10 * s)) {
|
|
bThickness = 10 * s;
|
|
} else if (bThickness < (5 * s)) {
|
|
bThickness = 5 * s;
|
|
}
|
|
|
|
min_thickness = bThickness;
|
|
natural_thickness = bThickness;
|
|
}
|
|
|
|
// unused?
|
|
void HistogramRGBArea::getPreferredLengthForThickness(int thickness, int& min_length, int& natural_length) const
|
|
{
|
|
getPreferredLength(min_length, natural_length);
|
|
}
|
|
|
|
bool HistogramRGBArea::getShow()
|
|
{
|
|
return(showMode);
|
|
}
|
|
|
|
void HistogramRGBArea::setShow(bool show)
|
|
{
|
|
showMode = show;
|
|
}
|
|
|
|
void HistogramRGBArea::updateBackBuffer (int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW)
|
|
{
|
|
if (!get_realized () || !showMode || !(options.histogramScopeType == ScopeType::HISTOGRAM || options.histogramScopeType == ScopeType::WAVEFORM)) {
|
|
return;
|
|
}
|
|
|
|
// Mostly not necessary, but should be in some case
|
|
GThreadLock lock; // All GUI access from idle_add callbacks or separate thread HAVE to be protected
|
|
|
|
Glib::RefPtr<Gdk::Window> window = get_window();
|
|
int winx, winy, winw, winh;
|
|
window->get_geometry(winx, winy, winw, winh);
|
|
|
|
double s = RTScalable::getScale();
|
|
|
|
// This will create or update the size of the BackBuffer::surface
|
|
setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, winw, winh, true);
|
|
|
|
if (surface) {
|
|
Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(surface);
|
|
|
|
cc->set_source_rgba (0., 0., 0., 0.);
|
|
cc->set_operator (Cairo::OPERATOR_CLEAR);
|
|
cc->paint ();
|
|
cc->set_operator (Cairo::OPERATOR_OVER);
|
|
|
|
cc->set_antialias(Cairo::ANTIALIAS_NONE);
|
|
cc->set_line_width (1.0 * s);
|
|
|
|
if ( r != -1 && g != -1 && b != -1 ) {
|
|
if (needRed) {
|
|
// Red
|
|
cc->set_source_rgb(1.0, 0.0, 0.0);
|
|
drawBar(cc, r, 255.0, winw, winh, s);
|
|
}
|
|
|
|
if (needGreen) {
|
|
// Green
|
|
cc->set_source_rgb(0.0, 1.0, 0.0);
|
|
drawBar(cc, g, 255.0, winw, winh, s);
|
|
}
|
|
|
|
if (needBlue) {
|
|
// Blue
|
|
cc->set_source_rgb(0.0, 0.4, 1.0);
|
|
drawBar(cc, b, 255.0, winw, winh, s);
|
|
}
|
|
|
|
if(
|
|
(needLuma || needChroma)
|
|
&& (options.histogramScopeType == ScopeType::HISTOGRAM
|
|
|| options.histogramScopeType == ScopeType::WAVEFORM)
|
|
) {
|
|
float Lab_L, Lab_a, Lab_b;
|
|
rtengine::Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, Lab_L, Lab_a, Lab_b, options.rtSettings.HistogramWorking);
|
|
|
|
if (needLuma) {
|
|
// Luma
|
|
cc->set_source_rgb(1.0, 1.0, 1.0);
|
|
drawBar(cc, Lab_L, 100.0, winw, winh, s);
|
|
}
|
|
|
|
if (needChroma && options.histogramScopeType == ScopeType::HISTOGRAM) {
|
|
// Chroma
|
|
double chromaval = sqrt(Lab_a * Lab_a + Lab_b * Lab_b) / 1.8;
|
|
cc->set_source_rgb(0.9, 0.9, 0.0);
|
|
drawBar(cc, chromaval, 100.0, winw, winh, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setDirty(false);
|
|
}
|
|
|
|
void HistogramRGBArea::update (int valh, int rh, int gh, int bh)
|
|
{
|
|
|
|
if (valh) {
|
|
val = valh;
|
|
r = rh;
|
|
g = gh;
|
|
b = bh;
|
|
valid = true;
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
harih->pending++;
|
|
|
|
idle_register.add(
|
|
[this]() -> bool
|
|
{
|
|
if (harih->destroyed) {
|
|
if (harih->pending == 1) {
|
|
delete harih;
|
|
} else {
|
|
--harih->pending;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
harih->harea->updateBackBuffer(-1, -1, -1);
|
|
harih->harea->queue_draw ();
|
|
|
|
--harih->pending;
|
|
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
|
|
void HistogramRGBArea::updateOptions (bool r, bool g, bool b, bool l, bool c, bool bar)
|
|
{
|
|
|
|
options.histogramRed = needRed = r;
|
|
options.histogramGreen = needGreen = g;
|
|
options.histogramBlue = needBlue = b;
|
|
options.histogramLuma = needLuma = l;
|
|
options.histogramChroma = needChroma = c;
|
|
options.histogramBar = showMode = bar;
|
|
|
|
}
|
|
|
|
void HistogramRGBArea::on_realize ()
|
|
{
|
|
|
|
Gtk::DrawingArea::on_realize();
|
|
add_events(Gdk::BUTTON_PRESS_MASK);
|
|
}
|
|
|
|
bool HistogramRGBArea::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
|
|
{
|
|
|
|
const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
|
|
style->render_background(cr, 0, 0, get_width(), get_height());
|
|
|
|
// on_realize & updateBackBuffer have to be called before
|
|
if (surface) {
|
|
if (isDirty()) { // not sure this could happen...
|
|
updateBackBuffer(-1, -1, -1);
|
|
}
|
|
|
|
copySurface(cr, NULL);
|
|
}
|
|
|
|
style->render_frame (cr, 0, 0, get_width(), get_height());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HistogramRGBArea::on_button_press_event (GdkEventButton* event)
|
|
{
|
|
|
|
if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
|
|
// do something?
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HistogramRGBArea::factorChanged (double newFactor)
|
|
{
|
|
factor = newFactor;
|
|
}
|
|
|
|
void HistogramRGBAreaHori::drawBar(Cairo::RefPtr<Cairo::Context> cc, double value, double max_value, int winw, int winh, double scale)
|
|
{
|
|
double pos;
|
|
if (options.histogramDrawMode < 2) {
|
|
pos = padding + value * (winw - padding * 2.0) / max_value + 0.5 * scale;
|
|
} else {
|
|
pos = padding + HistogramScaling::log (max_value, value) * (winw - padding * 2.0) / max_value + 0.5 * scale;
|
|
}
|
|
cc->move_to(pos, 0.0);
|
|
cc->line_to(pos, winh - 0.0);
|
|
cc->stroke();
|
|
}
|
|
|
|
Gtk::SizeRequestMode HistogramRGBAreaHori::get_request_mode_vfunc () const
|
|
{
|
|
return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
|
}
|
|
|
|
void HistogramRGBAreaHori::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
|
|
{
|
|
getPreferredThickness(minimum_height, natural_height);
|
|
}
|
|
|
|
void HistogramRGBAreaHori::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
|
|
{
|
|
getPreferredLength(minimum_width, natural_width);
|
|
}
|
|
|
|
void HistogramRGBAreaHori::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
|
|
{
|
|
getPreferredThicknessForLength(width, minimum_height, natural_height);
|
|
}
|
|
|
|
void HistogramRGBAreaHori::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
|
|
{
|
|
getPreferredLengthForThickness(height, minimum_width, natural_width);
|
|
}
|
|
|
|
void HistogramRGBAreaVert::drawBar(Cairo::RefPtr<Cairo::Context> cc, double value, double max_value, int winw, int winh, double scale)
|
|
{
|
|
double pos;
|
|
if (options.histogramDrawMode < 2 || options.histogramScopeType == ScopeType::WAVEFORM) {
|
|
pos = padding + value * (winh - padding * 2.0 - 1) / max_value + 0.5 * scale;
|
|
} else {
|
|
pos = padding + HistogramScaling::log (max_value, value) * (winh - padding * 2.0) / max_value + 0.5 * scale;
|
|
}
|
|
cc->move_to(0.0, winh - pos);
|
|
cc->line_to(winw, winh - pos);
|
|
cc->stroke();
|
|
}
|
|
|
|
Gtk::SizeRequestMode HistogramRGBAreaVert::get_request_mode_vfunc () const
|
|
{
|
|
return Gtk::SIZE_REQUEST_WIDTH_FOR_HEIGHT;
|
|
}
|
|
|
|
void HistogramRGBAreaVert::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
|
|
{
|
|
getPreferredLength(minimum_height, natural_height);
|
|
}
|
|
|
|
void HistogramRGBAreaVert::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
|
|
{
|
|
getPreferredThickness(minimum_width, natural_width);
|
|
}
|
|
|
|
void HistogramRGBAreaVert::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
|
|
{
|
|
getPreferredLengthForThickness(width, minimum_height, natural_height);
|
|
}
|
|
|
|
void HistogramRGBAreaVert::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
|
|
{
|
|
getPreferredThicknessForLength(height, minimum_width, natural_width);
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
// HistogramArea
|
|
HistogramArea::HistogramArea (DrawModeListener *fml) :
|
|
vectorscope_scale(0),
|
|
vect_hc(0, 0), vect_hs(0, 0),
|
|
vect_hc_buffer_dirty(true), vect_hs_buffer_dirty(true),
|
|
waveform_scale(0),
|
|
rwave(0, 0), gwave(0, 0),bwave(0, 0), lwave(0, 0),
|
|
wave_buffer_dirty(true),
|
|
valid(false), drawMode(options.histogramDrawMode), myDrawModeListener(fml),
|
|
scopeType(options.histogramScopeType),
|
|
oldwidth(-1), oldheight(-1),
|
|
trace_brightness(1.0),
|
|
needRed(options.histogramRed), needGreen(options.histogramGreen), needBlue(options.histogramBlue),
|
|
needLuma(options.histogramLuma), needChroma(options.histogramChroma),
|
|
isPressed(false), movingPosition(0.0),
|
|
pointer_red(-1), pointer_green(-1), pointer_blue(-1)
|
|
{
|
|
|
|
rhist(256);
|
|
ghist(256);
|
|
bhist(256);
|
|
lhist(256);
|
|
chist(256);
|
|
|
|
get_style_context()->add_class("drawingarea");
|
|
set_name("HistogramArea");
|
|
|
|
haih = new HistogramAreaIdleHelper;
|
|
haih->harea = this;
|
|
haih->destroyed = false;
|
|
haih->pending = 0;
|
|
}
|
|
|
|
HistogramArea::~HistogramArea ()
|
|
{
|
|
idle_register.destroy();
|
|
|
|
if (haih->pending) {
|
|
haih->destroyed = true;
|
|
} else {
|
|
delete haih;
|
|
}
|
|
}
|
|
|
|
Gtk::SizeRequestMode HistogramArea::get_request_mode_vfunc () const
|
|
{
|
|
return Gtk::SIZE_REQUEST_CONSTANT_SIZE;
|
|
}
|
|
|
|
void HistogramArea::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
|
|
{
|
|
int s = RTScalable::getScale();
|
|
minimum_height = 100 * s;
|
|
natural_height = 200 * s;
|
|
}
|
|
|
|
void HistogramArea::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
|
|
{
|
|
|
|
int s = RTScalable::getScale();
|
|
minimum_width = 200 * s;
|
|
natural_width = 400 * s;
|
|
}
|
|
|
|
void HistogramArea::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
|
|
{
|
|
|
|
minimum_height = 0;
|
|
natural_height = 0;
|
|
}
|
|
|
|
void HistogramArea::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
|
|
{
|
|
get_preferred_width_vfunc (minimum_width, natural_width);
|
|
}
|
|
|
|
void HistogramArea::updateOptions (bool r, bool g, bool b, bool l, bool c, int mode, ScopeType type, bool pointer)
|
|
{
|
|
|
|
options.histogramRed = needRed = r;
|
|
options.histogramGreen = needGreen = g;
|
|
options.histogramBlue = needBlue = b;
|
|
options.histogramLuma = needLuma = l;
|
|
options.histogramChroma = needChroma = c;
|
|
options.histogramDrawMode = drawMode = mode;
|
|
options.histogramScopeType = scopeType = type;
|
|
options.histogramBar = needPointer = pointer;
|
|
|
|
wave_buffer_dirty = true;
|
|
}
|
|
|
|
bool HistogramArea::updatePending(void)
|
|
{
|
|
return haih->pending > 0 && !haih->destroyed;
|
|
}
|
|
|
|
void HistogramArea::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
|
|
)
|
|
{
|
|
if (histRed) {
|
|
switch (scopeType) {
|
|
case ScopeType::HISTOGRAM:
|
|
rhist = histRed;
|
|
ghist = histGreen;
|
|
bhist = histBlue;
|
|
lhist = histLuma;
|
|
chist = histChroma;
|
|
break;
|
|
case ScopeType::HISTOGRAM_RAW:
|
|
rhistRaw = histRedRaw;
|
|
ghistRaw = histGreenRaw;
|
|
bhistRaw = histBlueRaw;
|
|
break;
|
|
case ScopeType::WAVEFORM:
|
|
waveform_scale = waveformScale;
|
|
rwave = waveformRed;
|
|
gwave = waveformGreen;
|
|
bwave = waveformBlue;
|
|
lwave = waveformLuma;
|
|
wave_buffer_dirty = true;
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HS:
|
|
vectorscope_scale = vectorscopeScale;
|
|
vect_hs = vectorscopeHS;
|
|
vect_hs_buffer_dirty = true;
|
|
break;
|
|
case ScopeType::VECTORSCOPE_HC:
|
|
vectorscope_scale = vectorscopeScale;
|
|
vect_hc = vectorscopeHC;
|
|
vect_hc_buffer_dirty = true;
|
|
break;
|
|
case ScopeType::NONE:
|
|
break;
|
|
}
|
|
valid = true;
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
haih->pending++;
|
|
|
|
// Can be done outside of the GUI thread
|
|
idle_register.add(
|
|
[this]() -> bool
|
|
{
|
|
if (haih->destroyed) {
|
|
if (haih->pending == 1) {
|
|
delete haih;
|
|
} else {
|
|
--haih->pending;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
haih->harea->setDirty(true);
|
|
haih->harea->updateBackBuffer();
|
|
haih->harea->queue_draw();
|
|
|
|
--haih->pending;
|
|
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
|
|
void HistogramArea::updateBackBuffer ()
|
|
{
|
|
if (!get_realized ()) {
|
|
return;
|
|
}
|
|
|
|
Glib::RefPtr<Gdk::Window> window = get_window();
|
|
int winx, winy, winw, winh;
|
|
window->get_geometry(winx, winy, winw, winh);
|
|
|
|
// This will create or update the size of the BackBuffer::surface
|
|
setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, winw, winh, true);
|
|
|
|
Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create(surface);
|
|
const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
|
|
|
|
double s = RTScalable::getScale();
|
|
|
|
// Setup drawing
|
|
cr->set_source_rgba (0., 0., 0., 0.);
|
|
cr->set_operator (Cairo::OPERATOR_CLEAR);
|
|
cr->paint ();
|
|
cr->set_operator (Cairo::OPERATOR_SOURCE);
|
|
|
|
// Prepare drawing gridlines first
|
|
cr->set_source_rgba (1., 1., 1., 0.25);
|
|
cr->set_line_width (1.0 * s);
|
|
cr->set_antialias(Cairo::ANTIALIAS_NONE);
|
|
cr->set_line_join(Cairo::LINE_JOIN_MITER);
|
|
cr->set_line_cap(Cairo::LINE_CAP_BUTT);
|
|
std::valarray<double> ch_ds (1);
|
|
ch_ds[0] = 4;
|
|
cr->set_dash (ch_ds, 0);
|
|
|
|
// determine the number of h-gridlines based on current h
|
|
int nrOfHGridPartitions = static_cast<int>(rtengine::min (16.0, pow (2.0, floor ((h - 100) / 250) + 2)));
|
|
int nrOfVGridPartitions = 8; // always show 8 stops (lines at 1,3,7,15,31,63,127)
|
|
|
|
// draw vertical gridlines
|
|
if (options.histogramScopeType == ScopeType::HISTOGRAM || options.histogramScopeType == ScopeType::HISTOGRAM_RAW) {
|
|
for (int i = 0; i <= nrOfVGridPartitions; i++) {
|
|
double xpos = padding + 0.5;
|
|
if (options.histogramDrawMode < 2) {
|
|
xpos += (pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0;
|
|
} else {
|
|
xpos += HistogramScaling::log (255, pow(2.0,i) - 1) * (w - padding * 2.0) / 255.0;
|
|
}
|
|
cr->move_to (xpos, 0.);
|
|
cr->line_to (xpos, h);
|
|
cr->stroke ();
|
|
}
|
|
}
|
|
|
|
// draw horizontal gridlines
|
|
if (options.histogramScopeType == ScopeType::WAVEFORM) {
|
|
for (int i = 0; i <= nrOfVGridPartitions; i++) {
|
|
const double ypos = h - padding - (pow(2.0,i) - 1) * (h - 2 * padding - 1) / 255.0;
|
|
cr->move_to(0, ypos);
|
|
cr->line_to(w, ypos);
|
|
cr->stroke();
|
|
}
|
|
} else if (options.histogramScopeType == ScopeType::VECTORSCOPE_HC || options.histogramScopeType == ScopeType::VECTORSCOPE_HS) {
|
|
// Vectorscope has no gridlines.
|
|
} else if (options.histogramDrawMode == 0) {
|
|
for (int i = 1; i < nrOfHGridPartitions; i++) {
|
|
cr->move_to (padding, i * static_cast<double>(h) / nrOfHGridPartitions + 0.5);
|
|
cr->line_to (w - padding, i * static_cast<double>(h) / nrOfHGridPartitions + 0.5);
|
|
cr->stroke ();
|
|
}
|
|
} else {
|
|
for (int i = 1; i < nrOfHGridPartitions; i++) {
|
|
cr->move_to (padding, h - HistogramScaling::log (h, i * static_cast<double>(h) / nrOfHGridPartitions) + 0.5);
|
|
cr->line_to (w - padding, h - HistogramScaling::log (h, i * static_cast<double>(h) / nrOfHGridPartitions) + 0.5);
|
|
cr->stroke ();
|
|
}
|
|
}
|
|
|
|
cr->unset_dash();
|
|
|
|
if (valid && (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)) {
|
|
bool rawMode = scopeType == ScopeType::HISTOGRAM_RAW;
|
|
|
|
// For RAW mode use the other hists
|
|
LUTu& rh = rawMode ? rhistRaw : rhist;
|
|
LUTu& gh = rawMode ? ghistRaw : ghist;
|
|
LUTu& bh = rawMode ? bhistRaw : bhist;
|
|
|
|
// make double copies of LUT, one for faster access, another one to scale down the raw histos
|
|
LUTu rhchanged(256), ghchanged(256), bhchanged(256);
|
|
unsigned int lhisttemp[256] ALIGNED16 {0}, chisttemp[256] ALIGNED16 {0}, rhtemp[256] ALIGNED16 {0}, ghtemp[256] ALIGNED16 {0}, bhtemp[256] ALIGNED16 {0};
|
|
const int scale = (rawMode ? 8 : 1);
|
|
|
|
for(int i = 0; i < 256; i++) {
|
|
if(needLuma) {
|
|
lhisttemp[i] = lhist[i];
|
|
}
|
|
|
|
if(needChroma) {
|
|
chisttemp[i] = chist[i];
|
|
}
|
|
|
|
if(needRed) {
|
|
rhchanged[i] = rhtemp[i] = rh[i] / scale;
|
|
}
|
|
|
|
if(needGreen) {
|
|
ghchanged[i] = ghtemp[i] = gh[i] / scale;
|
|
}
|
|
|
|
if(needBlue) {
|
|
bhchanged[i] = bhtemp[i] = bh[i] / scale;
|
|
}
|
|
}
|
|
|
|
// Compute the highest point of the histogram for scaling
|
|
// Values at far left and right end (0 and 255) are handled differently
|
|
|
|
unsigned int histheight = 0;
|
|
|
|
for (int i = 1; i < 255; i++) {
|
|
if (needLuma && lhisttemp[i] > histheight) {
|
|
histheight = lhisttemp[i];
|
|
}
|
|
|
|
if (needChroma && chisttemp[i] > histheight) {
|
|
histheight = chisttemp[i];
|
|
}
|
|
|
|
if (needRed && rhtemp[i] > histheight) {
|
|
histheight = rhtemp[i];
|
|
}
|
|
|
|
if (needGreen && ghtemp[i] > histheight) {
|
|
histheight = ghtemp[i];
|
|
}
|
|
|
|
if (needBlue && bhtemp[i] > histheight) {
|
|
histheight = bhtemp[i];
|
|
}
|
|
}
|
|
|
|
int realhistheight = histheight;
|
|
|
|
if (realhistheight < winh - 2) {
|
|
realhistheight = winh - 2;
|
|
}
|
|
|
|
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
|
|
cr->set_line_width (1.0 * s);
|
|
cr->set_operator (Cairo::OPERATOR_OVER);
|
|
|
|
int ui = 0, oi = 0;
|
|
|
|
if (needLuma && !rawMode) {
|
|
drawCurve(cr, lhist, realhistheight, w, h);
|
|
cr->set_source_rgba (0.65, 0.65, 0.65, 0.65);
|
|
cr->fill ();
|
|
drawMarks(cr, lhist, realhistheight, w, ui, oi);
|
|
}
|
|
|
|
if (needChroma && !rawMode) {
|
|
drawCurve(cr, chist, realhistheight, w, h);
|
|
cr->set_source_rgb (0.9, 0.9, 0.);
|
|
cr->stroke ();
|
|
drawMarks(cr, chist, realhistheight, w, ui, oi);
|
|
}
|
|
|
|
if (needRed) {
|
|
drawCurve(cr, rhchanged, realhistheight, w, h);
|
|
cr->set_source_rgb (1.0, 0.0, 0.0);
|
|
cr->stroke ();
|
|
drawMarks(cr, rhchanged, realhistheight, w, ui, oi);
|
|
}
|
|
|
|
if (needGreen) {
|
|
drawCurve(cr, ghchanged, realhistheight, w, h);
|
|
cr->set_source_rgb (0.0, 1.0, 0.0);
|
|
cr->stroke ();
|
|
drawMarks(cr, ghchanged, realhistheight, w, ui, oi);
|
|
}
|
|
|
|
if (needBlue) {
|
|
drawCurve(cr, bhchanged, realhistheight, w, h);
|
|
cr->set_source_rgb (0.0, 0.4, 1.0);
|
|
cr->stroke ();
|
|
drawMarks(cr, bhchanged, realhistheight, w, ui, oi);
|
|
}
|
|
|
|
} else if (scopeType == ScopeType::WAVEFORM && rwave.getWidth() > 0) {
|
|
drawWaveform(cr, w, h);
|
|
} else if (scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS) {
|
|
drawVectorscope(cr, w, h);
|
|
}
|
|
|
|
// Draw the frame's border
|
|
style->render_frame(cr, 0, 0, surface->get_width(), surface->get_height());
|
|
|
|
oldwidth = w;
|
|
oldheight = h;
|
|
|
|
setDirty(false);
|
|
}
|
|
|
|
bool HistogramArea::updatePointer(int r, int g, int b, const Glib::ustring &profile, const Glib::ustring &profileW)
|
|
{
|
|
if (!needPointer || !(scopeType == ScopeType::VECTORSCOPE_HC || scopeType == ScopeType::VECTORSCOPE_HS)) {
|
|
return false;
|
|
}
|
|
if (pointer_red == r && pointer_green == g && pointer_blue == b) {
|
|
return false;
|
|
}
|
|
|
|
float L;
|
|
pointer_red = r;
|
|
pointer_green = g;
|
|
pointer_blue = b;
|
|
Color::rgb2lab01(profile, profileW, r / 255.f, g / 255.f, b / 255.f, L, pointer_a, pointer_b, options.rtSettings.HistogramWorking);
|
|
updateBackBuffer();
|
|
return true;
|
|
}
|
|
|
|
void HistogramArea::on_realize ()
|
|
{
|
|
|
|
Gtk::DrawingArea::on_realize();
|
|
add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
|
|
}
|
|
|
|
void HistogramArea::drawCurve(Cairo::RefPtr<Cairo::Context> &cr,
|
|
const LUTu & data, double scale, int hsize, int vsize)
|
|
{
|
|
double s = RTScalable::getScale();
|
|
|
|
cr->set_line_width(s);
|
|
cr->move_to (padding, vsize - 1);
|
|
scale = scale <= 0.0 ? 0.001 : scale; // avoid division by zero and negative values
|
|
|
|
for (int i = 0; i < 256; i++) {
|
|
double val = data[i] * static_cast<double>(vsize) / scale;
|
|
|
|
if (drawMode > 0) { // scale y for single and double log-scale
|
|
val = HistogramScaling::log (static_cast<double>(vsize), val);
|
|
}
|
|
|
|
double iscaled = i;
|
|
if (drawMode == 2) { // scale x for double log-scale
|
|
iscaled = HistogramScaling::log (255.0, static_cast<double>(i));
|
|
}
|
|
|
|
double posX = padding + iscaled * (hsize - padding * 2.0) / 255.0;
|
|
double posY = vsize - 2 + val * (4 - vsize) / vsize;
|
|
|
|
cr->line_to (posX, posY);
|
|
}
|
|
|
|
cr->line_to (hsize - padding, vsize - 1);
|
|
}
|
|
|
|
void HistogramArea::drawMarks(Cairo::RefPtr<Cairo::Context> &cr,
|
|
const LUTu & data, double scale, int hsize, int & ui, int & oi)
|
|
{
|
|
int s = 8 * RTScalable::getScale();
|
|
|
|
if(data[0] > scale) {
|
|
cr->rectangle(padding, (ui++)*s, s, s);
|
|
}
|
|
|
|
if(data[255] > scale) {
|
|
cr->rectangle(hsize - s - padding, (oi++)*s, s, s);
|
|
}
|
|
|
|
cr->fill();
|
|
}
|
|
|
|
void HistogramArea::drawVectorscope(Cairo::RefPtr<Cairo::Context> &cr, int w, int h)
|
|
{
|
|
if (scopeType != ScopeType::VECTORSCOPE_HC && scopeType != ScopeType::VECTORSCOPE_HS) {
|
|
return;
|
|
}
|
|
|
|
const auto& vect = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc : vect_hs;
|
|
auto& vect_buffer = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer : vect_hs_buffer;
|
|
auto& vect_buffer_dirty = (scopeType == ScopeType::VECTORSCOPE_HC) ? vect_hc_buffer_dirty : vect_hs_buffer_dirty;
|
|
|
|
const int vect_width = vect.getWidth();
|
|
const int vect_height = vect.getHeight();
|
|
// Arbitrary scale factor multiplied by vectorscope area and divided by
|
|
// current scale.
|
|
const float scale = trace_brightness * 8.f * vect_width * vect_height / vectorscope_scale;
|
|
|
|
// See Cairo documentation on stride.
|
|
const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, vect_width);
|
|
|
|
if (vect_buffer_dirty && vectorscope_scale > 0) {
|
|
if (vect_buffer.size() != static_cast<std::size_t>(cairo_stride) * vect_height) {
|
|
vect_buffer.resize(static_cast<std::size_t>(cairo_stride) * vect_height);
|
|
}
|
|
|
|
assert(vect_buffer.size() % 4 == 0);
|
|
|
|
// TODO: Optimize.
|
|
for (int y = 0; y < vect_height; y++) {
|
|
const int* const vect_row = vect[y];
|
|
std::uint32_t* const buffer_row =
|
|
reinterpret_cast<uint32_t*>(vect_buffer.data() + (vect_height - 1 - y) * cairo_stride);
|
|
for (int x = 0; x < vect_width; x++) {
|
|
const unsigned char value = std::min<float>(scale * vect_row[x], 0xff);
|
|
buffer_row[x] = value | (value << 8) | (value << 16) | (value << 24);
|
|
}
|
|
}
|
|
|
|
vect_buffer_dirty = false;
|
|
}
|
|
|
|
const bool fit_width =
|
|
vect_width * (h - 2 * padding) > vect_height * (w - 2 * padding);
|
|
const float scope_scale = fit_width ?
|
|
(w - 2 * padding) / vect_width : (h - 2 * padding) / vect_height;
|
|
const float scope_size = (vectorscope_scale > 0) ?
|
|
scope_scale * std::max<double>(vect_width, vect_height) : std::min<float>(w, h) - 2 * padding;
|
|
const float o_x = (w - scope_scale * vect_width) / 2;
|
|
const float o_y = (h - scope_scale * vect_height) / 2;
|
|
const double s = RTScalable::getScale();
|
|
auto orig_matrix = cr->get_matrix();
|
|
const double line_spacing = 4.0 * s;
|
|
const double line_length = scope_size / 2.0 - 2.0 * line_spacing;
|
|
std::valarray<double> ch_ds(1);
|
|
|
|
cr->translate(w / 2.0, h / 2.0);
|
|
cr->set_line_width (1.0 * s);
|
|
cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
|
|
ch_ds[0] = 4;
|
|
|
|
if (scopeType == ScopeType::VECTORSCOPE_HS) { // Hue-Saturation.
|
|
// RYGCBM lines.
|
|
cr->set_line_width (2.0 * s);
|
|
constexpr double color_labels[6][3] = {
|
|
{1, 0, 0}, // R
|
|
{1, 1, 0}, // Y
|
|
{0, 1, 0}, // G
|
|
{0, 1, 1}, // C
|
|
{0, 0, 1}, // B
|
|
{1, 0, 1}, // M
|
|
};
|
|
for (int i = 0; i < 6; i++) {
|
|
auto gradient = Cairo::LinearGradient::create(0, 0, scope_size / 2.0, 0);
|
|
const double (&color)[3] = color_labels[i];
|
|
cr->set_source(gradient);
|
|
gradient->add_color_stop_rgba(0, 1, 1, 1, 0.25);
|
|
gradient->add_color_stop_rgba(1, color[0], color[1], color[2], 0.5);
|
|
cr->move_to(line_spacing, 0);
|
|
cr->line_to(line_spacing + line_length, 0);
|
|
cr->rotate_degrees(-60);
|
|
cr->stroke();
|
|
}
|
|
cr->set_line_width (1.0 * s);
|
|
cr->set_source_rgba (1, 1, 1, 0.25);
|
|
// 100% saturation circle.
|
|
cr->arc(0, 0, scope_size / 2.0, 0, 2 * RT_PI);
|
|
cr->stroke();
|
|
// 25%, 50%, and 75% saturation.
|
|
cr->set_dash(ch_ds, 0);
|
|
for (int i = 1; i < 4; i++) {
|
|
cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI);
|
|
cr->stroke();
|
|
}
|
|
// HSV skin tone line derived from -I axis of YIQ.
|
|
cr->rotate(-0.134900 * RT_PI);
|
|
cr->move_to(line_spacing, 0);
|
|
cr->line_to(line_spacing + line_length, 0);
|
|
cr->stroke();
|
|
cr->unset_dash();
|
|
} else if (scopeType == ScopeType::VECTORSCOPE_HC) { // Hue-Chroma.
|
|
// a and b axes.
|
|
Cairo::RefPtr<Cairo::LinearGradient> gradient;
|
|
cr->set_line_width (2.0 * s);
|
|
gradient = Cairo::LinearGradient::create(0, -scope_size / 2.0, 0, scope_size / 2.0);
|
|
cr->set_source(gradient);
|
|
gradient->add_color_stop_rgba(0, 1, 1, 0, 0.5); // "yellow"
|
|
gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral
|
|
gradient->add_color_stop_rgba(1, 0, 0, 1, 0.5); // "blue"
|
|
cr->move_to(0, line_spacing);
|
|
cr->line_to(0, line_spacing + line_length);
|
|
cr->move_to(0, -line_spacing);
|
|
cr->line_to(0, -line_spacing - line_length);
|
|
cr->stroke();
|
|
gradient = Cairo::LinearGradient::create(-scope_size / 2.0, 0, scope_size / 2.0, 0);
|
|
cr->set_source(gradient);
|
|
gradient->add_color_stop_rgba(0, 0, 1, 0, 0.5); // "green"
|
|
gradient->add_color_stop_rgba(0.5, 1, 1, 1, 0.25); // neutral
|
|
gradient->add_color_stop_rgba(1, 1, 0, 1, 0.5); // "magenta"
|
|
cr->move_to(line_spacing, 0);
|
|
cr->line_to(line_spacing + line_length, 0);
|
|
cr->move_to(-line_spacing, 0);
|
|
cr->line_to(-line_spacing - line_length, 0);
|
|
cr->stroke();
|
|
cr->set_source_rgba (1, 1, 1, 0.25);
|
|
cr->set_line_width (1.0 * s);
|
|
// 25%, 50%, 75%, and 100% of standard chroma range.
|
|
cr->set_dash(ch_ds, 0);
|
|
for (int i = 1; i <= 4; i++) {
|
|
cr->arc(0, 0, i * scope_size / 8.0, 0, 2 * RT_PI);
|
|
cr->stroke();
|
|
}
|
|
// CIELAB skin tone line, approximated by 50% saturation and
|
|
// value along the HSV skin tone line.
|
|
cr->rotate(-0.321713 * RT_PI);
|
|
cr->move_to(line_spacing, 0);
|
|
cr->line_to(line_spacing + line_length, 0);
|
|
cr->stroke();
|
|
cr->unset_dash();
|
|
}
|
|
cr->set_matrix(orig_matrix);
|
|
|
|
// Vectorscope trace.
|
|
if (vectorscope_scale > 0) {
|
|
Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(
|
|
vect_buffer.data(), Cairo::FORMAT_ARGB32, vect_width, vect_height, cairo_stride);
|
|
cr->translate(o_x, o_y);
|
|
cr->scale(scope_scale, scope_scale);
|
|
cr->set_source(surface, 0, 0);
|
|
cr->set_operator(Cairo::OPERATOR_OVER);
|
|
cr->paint();
|
|
surface->finish();
|
|
cr->set_matrix(orig_matrix);
|
|
|
|
if (needPointer && pointer_red >= 0 && pointer_green >= 0 && pointer_blue >= 0) {
|
|
float cx, cy;
|
|
if (scopeType == ScopeType::VECTORSCOPE_HS) {
|
|
float H, S, L;
|
|
Color::rgb2hslfloat(pointer_red * 257.f, pointer_green * 257.f, pointer_blue * 257.f, H, S, L);
|
|
cx = (w + scope_size * S * std::cos(H * 2 * RT_PI_F)) / 2;
|
|
cy = (h - scope_size * S * std::sin(H * 2 * RT_PI_F)) / 2;
|
|
} else {
|
|
constexpr float ab_factor = 327.68f / 96000.f;
|
|
cx = w / 2.f + scope_size * pointer_a * ab_factor;
|
|
cy = h / 2.f - scope_size * pointer_b * ab_factor;
|
|
}
|
|
cr->set_source_rgba(1, 1, 1, 0.5);
|
|
cr->set_dash(ch_ds, 0);
|
|
cr->move_to(0, cy);
|
|
cr->line_to(w, cy);
|
|
cr->move_to(cx, 0);
|
|
cr->line_to(cx, h);
|
|
cr->stroke();
|
|
cr->unset_dash();
|
|
cr->arc(cx, cy, 3 * s, 0, 2 * RT_PI);
|
|
cr->set_source_rgb(1, 1, 1);
|
|
cr->fill_preserve();
|
|
cr->set_source_rgb(0, 0, 0);
|
|
cr->set_line_width (1.0 * s);
|
|
cr->stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
void HistogramArea::drawWaveform(Cairo::RefPtr<Cairo::Context> &cr, int w, int h)
|
|
{
|
|
// Arbitrary scale factor divided by current scale.
|
|
const float scale = trace_brightness * 32.f * 255.f / waveform_scale;
|
|
const int wave_width = rwave.getWidth();
|
|
const int wave_height = rwave.getHeight();
|
|
|
|
// See Cairo documentation on stride.
|
|
const int cairo_stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_ARGB32, rwave.getWidth());
|
|
|
|
if (wave_buffer_dirty) {
|
|
const auto buffer_size = static_cast<std::vector<unsigned char>::size_type>(wave_height) * cairo_stride;
|
|
wave_buffer.assign(buffer_size, 0);
|
|
wave_buffer_luma.assign(buffer_size, 0);
|
|
|
|
assert(wave_buffer.size() % 4 == 0);
|
|
assert(wave_buffer_luma.size() % 4 == 0);
|
|
|
|
// TODO: Optimize.
|
|
for (int val = 0; val < wave_height; val++) {
|
|
const int* const r_row = rwave[val];
|
|
const int* const g_row = gwave[val];
|
|
const int* const b_row = bwave[val];
|
|
std::uint32_t* const buffer_row = reinterpret_cast<uint32_t*>(wave_buffer.data() + (255 - val) * cairo_stride);
|
|
for (int col = 0; col < wave_width; col++) {
|
|
const unsigned char r = needRed ? std::min<float>(scale * r_row[col], 0xff) : 0;
|
|
const unsigned char g = needGreen ? std::min<float>(scale * g_row[col], 0xff) : 0;
|
|
const unsigned char b = needBlue ? std::min<float>(scale * b_row[col], 0xff) : 0;
|
|
const unsigned char value = rtengine::max(r, g, b);
|
|
if (value == 0) {
|
|
buffer_row[col] = 0;
|
|
} else {
|
|
// Speedup with one memory access instead of four.
|
|
buffer_row[col] = b | (g << 8) | (r << 16) | (value << 24);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needLuma) {
|
|
for (int val = 0; val < wave_height; val++) {
|
|
const int* const l_row = lwave[val];
|
|
std::uint32_t* const buffer_row =
|
|
reinterpret_cast<uint32_t*>(wave_buffer_luma.data() + (255 - val) * cairo_stride);
|
|
for (int col = 0; col < wave_width; col++) {
|
|
const unsigned char l = std::min<float>(scale * l_row[col], 0xff);
|
|
buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24);
|
|
}
|
|
}
|
|
}
|
|
|
|
wave_buffer_dirty = false;
|
|
}
|
|
|
|
Cairo::RefPtr<Cairo::ImageSurface> surface;
|
|
auto orig_matrix = cr->get_matrix();
|
|
cr->translate(0, padding);
|
|
cr->scale(static_cast<double>(w) / wave_width, (h - 2 * padding) / wave_height);
|
|
if (needLuma) {
|
|
surface = Cairo::ImageSurface::create(
|
|
wave_buffer_luma.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride);
|
|
cr->set_source(surface, 0, 0);
|
|
cr->set_operator(Cairo::OPERATOR_OVER);
|
|
cr->paint();
|
|
surface->finish();
|
|
}
|
|
surface = Cairo::ImageSurface::create(
|
|
wave_buffer.data(), Cairo::FORMAT_ARGB32, wave_width, wave_height, cairo_stride);
|
|
cr->set_source(surface, 0, 0);
|
|
cr->set_operator(Cairo::OPERATOR_OVER);
|
|
cr->paint();
|
|
surface->finish();
|
|
cr->set_matrix(orig_matrix);
|
|
}
|
|
|
|
bool HistogramArea::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
|
|
{
|
|
|
|
if (!updatePending() && (get_width() != oldwidth || get_height() != oldheight || isDirty())) {
|
|
updateBackBuffer ();
|
|
}
|
|
|
|
const Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
|
|
style->render_background(cr, 0, 0, get_width(), get_height());
|
|
copySurface(cr, NULL);
|
|
style->render_frame (cr, 0, 0, get_width(), get_height());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HistogramArea::on_button_press_event (GdkEventButton* event)
|
|
{
|
|
isPressed = true;
|
|
movingPosition = event->x;
|
|
|
|
if (
|
|
event->type == GDK_2BUTTON_PRESS && event->button == 1
|
|
&& (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)
|
|
) {
|
|
|
|
drawMode = (drawMode + 1) % 3;
|
|
options.histogramDrawMode = (options.histogramDrawMode + 1) % 3;
|
|
|
|
if (myDrawModeListener) {
|
|
myDrawModeListener->toggleButtonMode ();
|
|
}
|
|
|
|
updateBackBuffer ();
|
|
queue_draw ();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HistogramArea::on_button_release_event (GdkEventButton* event)
|
|
{
|
|
isPressed = false;
|
|
return true;
|
|
}
|
|
|
|
bool HistogramArea::on_motion_notify_event (GdkEventMotion* event)
|
|
{
|
|
if (
|
|
drawMode == 0
|
|
&& (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (!isPressed) {
|
|
return true;
|
|
}
|
|
|
|
if (scopeType == ScopeType::HISTOGRAM || scopeType == ScopeType::HISTOGRAM_RAW) { // Adjust log scale.
|
|
double mod = 1 + (event->x - movingPosition) / get_width();
|
|
|
|
factor /= mod;
|
|
if (factor < 1.0)
|
|
factor = 1.0;
|
|
if (factor > 100.0)
|
|
factor = 100.0;
|
|
|
|
sigFactorChanged.emit(factor);
|
|
|
|
setDirty(true);
|
|
queue_draw ();
|
|
} else if (
|
|
scopeType == ScopeType::WAVEFORM
|
|
|| scopeType == ScopeType::VECTORSCOPE_HC
|
|
|| scopeType == ScopeType::VECTORSCOPE_HS
|
|
) { // Adjust brightness.
|
|
constexpr float MIN_BRIGHT = 0.1;
|
|
constexpr float MAX_BRIGHT = 3;
|
|
constexpr float RANGE = MAX_BRIGHT / MIN_BRIGHT;
|
|
double dx = (event->x - movingPosition) / get_width();
|
|
float new_brightness = LIM<float>(trace_brightness * pow(RANGE, dx), MIN_BRIGHT, MAX_BRIGHT);
|
|
if (new_brightness != trace_brightness) {
|
|
wave_buffer_dirty = vect_hc_buffer_dirty = vect_hs_buffer_dirty = true;
|
|
trace_brightness = new_brightness;
|
|
setDirty(true);
|
|
queue_draw();
|
|
}
|
|
movingPosition = event->x;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
HistogramArea::type_signal_factor_changed HistogramArea::signal_factor_changed()
|
|
{
|
|
return sigFactorChanged;
|
|
}
|