/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * 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 . */ #include "histogrampanel.h" #include "multilangmgr.h" #include "guiutils.h" #include "options.h" #include #include #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 2: case 3: histogramRGBArea = nullptr; break; case 1: histogramRGBArea = histogramRGBAreaVert.get(); break; default: histogramRGBArea = histogramRGBAreaHori.get(); } // 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"); rawImage = new RTImage ("histogram-bayer-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"); rawImage_g = new RTImage ("histogram-bayer-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")); 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")); 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 ()); showRAW = Gtk::manage (new Gtk::ToggleButton ()); showMode = Gtk::manage (new Gtk::Button ()); scopeType = Gtk::manage (new Gtk::Button ()); showBAR = Gtk::manage (new Gtk::ToggleButton ()); 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); showRAW->set_name("histButton"); showRAW->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); 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); showRAW->set_relief (Gtk::RELIEF_NONE); showMode->set_relief (Gtk::RELIEF_NONE); scopeType->set_relief (Gtk::RELIEF_NONE); showBAR->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")); showRAW->set_tooltip_text (M("HISTOGRAM_TOOLTIP_RAW")); showMode->set_tooltip_text (M("HISTOGRAM_TOOLTIP_MODE")); scopeType->set_tooltip_text (M("HISTOGRAM_TOOLTIP_TYPE")); showBAR->set_tooltip_text (M("HISTOGRAM_TOOLTIP_BAR")); buttonGrid = Gtk::manage (new Gtk::Grid ()); buttonGrid->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); showRAW->set_active (options.histogramRAW); // no showMode->set_active(), as it's not a ToggleButton 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); showRAW->set_image (showRAW->get_active() ? *rawImage : *rawImage_g); if (options.histogramDrawMode == 0) showMode->set_image(*mode0Image); else if (options.histogramDrawMode == 1) showMode->set_image(*mode1Image); else showMode->set_image(*mode2Image); if (options.histogramScopeType == 0) { scopeType->set_image(*histImage); } else if (options.histogramScopeType == 1) { scopeType->set_image(*waveImage); } else if (options.histogramScopeType == 2) { scopeType->set_image(*vectHsImage); } else if (options.histogramScopeType == 3) { scopeType->set_image(*vectHcImage); } showBAR->set_image (showBAR->get_active() ? *barImage : *barImage_g); raw_toggled(); // Make sure the luma/chroma toggles are enabled or disabled 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(showRAW , 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 ); showRAW->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::raw_toggled), showRAW ); showMode->signal_released().connect( sigc::mem_fun(*this, &HistogramPanel::mode_released), showMode ); scopeType->signal_pressed().connect( sigc::mem_fun(*this, &HistogramPanel::type_pressed), scopeType ); showBAR->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::bar_toggled), showBAR ); buttonGrid->add (*showRed); buttonGrid->add (*showGreen); buttonGrid->add (*showBlue); buttonGrid->add (*showValue); buttonGrid->add (*showChro); buttonGrid->add (*showRAW); buttonGrid->add (*showMode); buttonGrid->add (*scopeType); buttonGrid->add (*showBAR); // Put the button vbox next to the window's border to be less disturbing if (options.histogramPosition == 1) { add (*buttonGrid); add (*gfxGrid); } else { add (*gfxGrid); add (*buttonGrid); } show_all (); 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 rawImage; delete mode0Image; delete mode1Image; delete mode2Image; delete barImage; delete redImage_g; delete greenImage_g; delete blueImage_g; delete valueImage_g; delete chroImage_g; delete rawImage_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 < 2); } void HistogramPanel::resized (Gtk::Allocation& req) { 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::raw_toggled () { if (showRAW->get_active()) { showRAW->set_image(*rawImage); showValue->set_sensitive(false); showChro->set_sensitive(false); } else { showRAW->set_image(*rawImage_g); showValue->set_sensitive(options.histogramScopeType != 1); showChro->set_sensitive(options.histogramScopeType != 1); } 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() { constexpr int TYPE_COUNT = 4; // Histogram, waveform, and 2 vectorscopes. options.histogramScopeType = (options.histogramScopeType + 1) % TYPE_COUNT; if (options.histogramScopeType == 0) { scopeType->set_image(*histImage); } else if (options.histogramScopeType == 1) { scopeType->set_image(*waveImage); } else if (options.histogramScopeType == 2) { scopeType->set_image(*vectHsImage); } else if (options.histogramScopeType == 3) { scopeType->set_image(*vectHcImage); } type_changed(); rgbv_toggled(); } void HistogramPanel::type_changed() { if (showBAR->get_active() && histogramRGBArea) { histogramRGBArea->setShow(false); gfxGrid->remove(*histogramRGBArea); } if (options.histogramScopeType == 0) { showRed->set_sensitive(); showGreen->set_sensitive(); showBlue->set_sensitive(); showValue->set_sensitive(!showRAW->get_active()); showChro->set_sensitive(!showRAW->get_active()); showRAW->set_sensitive(); showMode->set_sensitive(); histogramRGBArea = histogramRGBAreaHori.get(); if (panel_listener) { updateHistAreaOptions(); panel_listener->scopeTypeChanged(HistogramPanelListener::HISTOGRAM); } } else if (options.histogramScopeType == 1) { showRed->set_sensitive(); showGreen->set_sensitive(); showBlue->set_sensitive(); showValue->set_sensitive(); showChro->set_sensitive(false); showRAW->set_sensitive(false); showMode->set_sensitive(false); histogramRGBArea = histogramRGBAreaVert.get(); if (panel_listener) { updateHistAreaOptions(); panel_listener->scopeTypeChanged(HistogramPanelListener::WAVEFORM); } } else { showRed->set_sensitive(false); showGreen->set_sensitive(false); showBlue->set_sensitive(false); showValue->set_sensitive(false); showChro->set_sensitive(false); showRAW->set_sensitive(false); showMode->set_sensitive(false); histogramRGBArea = nullptr; if (panel_listener) { updateHistAreaOptions(); HistogramPanelListener::ScopeType type; switch (options.histogramScopeType) { case 2: type = HistogramPanelListener::VECTORSCOPE_HS; break; case 3: type = HistogramPanelListener::VECTORSCOPE_CH; break; } panel_listener->scopeTypeChanged(type); } } 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) { histogramRGBArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), showBAR->get_active() && options.histogramScopeType < 2); 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(); } else { buttonGrid->reference(); removeIfThere(this, buttonGrid, false); add (*buttonGrid); buttonGrid->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) { HistogramPanelListener::ScopeType type; if (options.histogramScopeType == 0) { type = HistogramPanelListener::HISTOGRAM; } else if (options.histogramScopeType == 1) { type = HistogramPanelListener::WAVEFORM; } else if (options.histogramScopeType == 2) { type = HistogramPanelListener::VECTORSCOPE_HS; } else if (options.histogramScopeType == 3) { type = HistogramPanelListener::VECTORSCOPE_CH; } else { type = HistogramPanelListener::NONE; } listener->scopeTypeChanged(type); } } void HistogramPanel::updateHistAreaOptions() { histogramArea->updateOptions( showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active(), showChro->get_active(), showRAW->get_active(), options.histogramDrawMode, options.histogramScopeType, 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), rawMode(options.histogramRAW), 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 || (rawMode && options.histogramScopeType != 1)) { 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 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 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 <= 1) { 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 == 0) { // 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 raw, bool bar) { options.histogramRed = needRed = r; options.histogramGreen = needGreen = g; options.histogramBlue = needBlue = b; options.histogramLuma = needLuma = l; options.histogramChroma = needChroma = c; options.histogramRAW = rawMode = raw; 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 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 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 cc, double value, double max_value, int winw, int winh, double scale) { double pos; if (options.histogramDrawMode < 2 || options.histogramScopeType == 1) { 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(0, 0), vect_buffer_dirty(true), vect_buffer_size(0), 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), rawMode(options.histogramRAW), 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 = (int)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 = (int)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, bool raw, int mode, int type, bool pointer) { options.histogramRed = needRed = r; options.histogramGreen = needGreen = g; options.histogramBlue = needBlue = b; options.histogramLuma = needLuma = l; options.histogramChroma = needChroma = c; options.histogramRAW = rawMode = raw; options.histogramDrawMode = drawMode = mode; options.histogramScopeType = scopeType = type; options.histogramBar = needPointer = pointer; wave_buffer_dirty = true; } 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& vectorscope, int waveformScale, const array2D& waveformRed, const array2D& waveformGreen, const array2D& waveformBlue, const array2D& waveformLuma ) { if (histRed) { if (scopeType == 0) { rhist = histRed; ghist = histGreen; bhist = histBlue; lhist = histLuma; chist = histChroma; rhistRaw = histRedRaw; ghistRaw = histGreenRaw; bhistRaw = histBlueRaw; } else if (scopeType == 1) { const int wave_width = waveformRed.getWidth(); const int wave_height = waveformRed.getHeight(); waveform_scale = waveformScale; if (wave_width != rwave.getWidth() || wave_height != rwave.getHeight()) { rwave(wave_width, wave_height); gwave(wave_width, wave_height); bwave(wave_width, wave_height); lwave(wave_width, wave_height); } memcpy((int*)rwave, (const int*)waveformRed, wave_height * wave_width * sizeof(rwave[0][0])); memcpy((int*)gwave, (const int*)waveformGreen, wave_height * wave_width * sizeof(gwave[0][0])); memcpy((int*)bwave, (const int*)waveformBlue, wave_height * wave_width * sizeof(bwave[0][0])); memcpy((int*)lwave, (const int*)waveformLuma, wave_height * wave_width * sizeof(lwave[0][0])); wave_buffer_dirty = true; } else if (scopeType >= 2) { vectorscope_scale = vectorscopeScale; if (vect.getWidth() != vectorscope.getWidth() || vect.getHeight() != vectorscope.getHeight()) { vect(vectorscope.getWidth(), vectorscope.getHeight()); } memcpy((int*)vect, (const int*)vectorscope, vect.getHeight() * vect.getWidth() * sizeof(vect[0][0])); vect_buffer_dirty = true; } 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 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 cr = Cairo::Context::create(surface); const Glib::RefPtr 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 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 = (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 == 0) { 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 == 1) { 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 >= 2) { // Vectorscope has no gridlines. } else if (options.histogramDrawMode == 0) { for (int i = 1; i < nrOfHGridPartitions; i++) { cr->move_to (padding, i * (double)h / nrOfHGridPartitions + 0.5); cr->line_to (w - padding, i * (double)h / nrOfHGridPartitions + 0.5); cr->stroke (); } } else { for (int i = 1; i < nrOfHGridPartitions; i++) { cr->move_to (padding, h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5); cr->line_to (w - padding, h - HistogramScaling::log (h, i * (double)h / nrOfHGridPartitions) + 0.5); cr->stroke (); } } cr->unset_dash(); if (valid && scopeType == 0) { // 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 == 1 && rwave.getWidth() > 0) { drawWaveform(cr, w, h); } else if (scopeType >= 2) { 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 < 2) { 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 &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] * (double)vsize / scale; if (drawMode > 0) { // scale y for single and double log-scale val = HistogramScaling::log ((double)vsize, val); } double iscaled = i; if (drawMode == 2) { // scale x for double log-scale iscaled = HistogramScaling::log (255.0, (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 &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 &cr, int w, int h) { 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 != cairo_stride * vect_height) { vect_buffer_size = cairo_stride * vect_height; vect_buffer.reset(new unsigned char[vect_buffer_size]); } // TODO: Optimize. for (int y = 0; y < vect_height; y++) { int* vect_row = vect[y]; uint32_t* buffer_row = (uint32_t*)&(vect_buffer[(vect_height - 1 - y) * cairo_stride]); for (int x = 0; x < vect_width; x++) { const unsigned char value = min(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 * max(vect_width, vect_height) : min(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 ch_ds(1); cr->translate(w / 2.0, h / 2.0); cr->set_source_rgba (1., 1., 1., 0.25); cr->set_line_width (1.0 * s); cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL); ch_ds[0] = 4; if (scopeType == 2) { // Hue-Saturation. // RYGCBM lines. for (int i = 0; i < 6; i++) { cr->move_to(line_spacing, 0); cr->line_to(line_spacing + line_length, 0); cr->rotate_degrees(60); } cr->stroke(); // 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 == 3) { // Hue-Chroma. // a and b axes. 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->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(); // 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 surface = Cairo::ImageSurface::create( vect_buffer.get(), 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 == 2) { float H, S, L; Color::rgb2hsl(pointer_red * 256.f, pointer_green * 256.f, pointer_blue * 256.f, H, S, L); cx = (w + scope_size * S * cos(H * 2 * RT_PI)) / 2; cy = (h - scope_size * S * sin(H * 2 * RT_PI)) / 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 &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) { wave_buffer.reset(new unsigned char[wave_height * cairo_stride]); wave_buffer_luma.reset(new unsigned char[wave_height * cairo_stride]); // Clear waveform. memset(wave_buffer.get(), 0, wave_height * cairo_stride); memset(wave_buffer_luma.get(), 0, wave_height * cairo_stride); // TODO: Optimize. for (int val = 0; val < wave_height; val++) { int* r_row = rwave[val]; int* g_row = gwave[val]; int* b_row = bwave[val]; uint32_t* buffer_row = (uint32_t*)&(wave_buffer[(255 - val) * cairo_stride]); for (int col = 0; col < wave_width; col++) { const unsigned char r = needRed ? min(scale * r_row[col], 0xff) : 0; const unsigned char g = needGreen ? min(scale * g_row[col], 0xff) : 0; const unsigned char b = needBlue ? min(scale * b_row[col], 0xff) : 0; const unsigned char value = (r > g && r > b) ? r : ((g > b) ? 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++) { int* l_row = lwave[val]; uint32_t* buffer_row = (uint32_t*)&(wave_buffer_luma[(255 - val) * cairo_stride]); for (int col = 0; col < wave_width; col++) { const unsigned char l = min(scale * l_row[col], 0xff); buffer_row[col] = l | (l << 8) | (l << 16) | (l << 24); } } } wave_buffer_dirty = false; } Cairo::RefPtr surface; auto orig_matrix = cr->get_matrix(); cr->translate(0, padding); cr->scale(static_cast(w) / wave_width, (h - 2 * padding) / wave_height); if (needLuma) { surface = Cairo::ImageSurface::create( wave_buffer_luma.get(), 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.get(), 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 (get_width() != oldwidth || get_height() != oldheight || isDirty ()) { updateBackBuffer (); } const Glib::RefPtr 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) { 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 == 0) { return false; } if (!isPressed) { return true; } if (scopeType == 0) { // 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 >= 1 && scopeType <= 3) { // 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(trace_brightness * pow(RANGE, dx), MIN_BRIGHT, MAX_BRIGHT); if (new_brightness != trace_brightness) { wave_buffer_dirty = true; vect_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; }