rawTherapee/rtgui/histogrampanel.cc

475 lines
14 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 <http://www.gnu.org/licenses/>.
*/
#include <histogrampanel.h>
#include <multilangmgr.h>
#include <string.h>
HistogramPanel::HistogramPanel () {
histogramArea = Gtk::manage (new HistogramArea ());
showRed = Gtk::manage (new Gtk::ToggleButton (M("HISTOGRAM_BUTTON_R")));
showGreen = Gtk::manage (new Gtk::ToggleButton (M("HISTOGRAM_BUTTON_G")));
showBlue = Gtk::manage (new Gtk::ToggleButton (M("HISTOGRAM_BUTTON_B")));
showValue = Gtk::manage (new Gtk::ToggleButton (M("HISTOGRAM_BUTTON_L")));
Gtk::VBox* vbox = Gtk::manage (new Gtk::VBox (false, 0));
showRed->set_active (true);
showGreen->set_active (true);
showBlue->set_active (true);
showValue->set_active (true);
vbox->pack_start (*showRed, Gtk::PACK_SHRINK, 2);
vbox->pack_start (*showGreen, Gtk::PACK_SHRINK, 2);
vbox->pack_start (*showBlue, Gtk::PACK_SHRINK, 2);
vbox->pack_start (*showValue, Gtk::PACK_SHRINK, 2);
pack_start (*histogramArea);
pack_end (*vbox, Gtk::PACK_SHRINK, 2);
showRed->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::rgbv_toggled) );
showGreen->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::rgbv_toggled) );
showBlue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::rgbv_toggled) );
showValue->signal_toggled().connect( sigc::mem_fun(*this, &HistogramPanel::rgbv_toggled) );
show_all ();
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"));
rconn = signal_size_allocate().connect( sigc::mem_fun(*this, &HistogramPanel::resized) );
}
void HistogramPanel::resized (Gtk::Allocation& req) {
rconn.block (true);
if (req.get_width()/2>150)
set_size_request (req.get_width(), 150);
else
set_size_request (req.get_width(), req.get_width()/2);
rconn.block (false);
histogramArea->renderHistogram ();
histogramArea->queue_draw ();
}
void HistogramPanel::rgbv_toggled () {
histogramArea->updateOptions (showRed->get_active(), showGreen->get_active(), showBlue->get_active(), showValue->get_active());
histogramArea->queue_draw ();
}
HistogramArea::HistogramArea () :
valid(false), showFull(true), oldwidth(-1), needVal(true), needRed(true), needGreen(true), needBlue(true) {
haih = new HistogramAreaIdleHelper;
haih->harea = this;
haih->destroyed = false;
haih->pending = 0;
signal_style_changed().connect( sigc::mem_fun(*this, &HistogramArea::styleChanged) );
}
HistogramArea::~HistogramArea () {
if (haih->pending)
haih->destroyed = true;
else
delete haih;
}
void HistogramArea::updateOptions (bool r, bool g, bool b, bool v) {
needRed = r;
needGreen = g;
needBlue = b;
needVal = v;
renderHistogram ();
}
int histupdate (void* data) {
gdk_threads_enter ();
HistogramAreaIdleHelper* haih = (HistogramAreaIdleHelper*)data;
if (haih->destroyed) {
if (haih->pending == 1)
delete haih;
else
haih->pending--;
gdk_threads_leave ();
return 0;
}
haih->harea->renderHistogram ();
haih->harea->queue_draw ();
haih->pending--;
gdk_threads_leave ();
return 0;
}
void HistogramArea::update (unsigned int* rh, unsigned int* gh, unsigned int* bh, unsigned int* lh) {
if (rh!=NULL) {
memcpy (lhist, lh, 256*sizeof(unsigned int));
memcpy (rhist, rh, 256*sizeof(unsigned int));
memcpy (ghist, gh, 256*sizeof(unsigned int));
memcpy (bhist, bh, 256*sizeof(unsigned int));
valid = true;
}
else
valid = false;
haih->pending++;
g_idle_add (histupdate, haih);
}
void HistogramArea::renderHistogram () {
if (!is_realized ())
return;
Glib::RefPtr<Gdk::Window> window = get_window();
int winx, winy, winw, winh, wind;
window->get_geometry(winx, winy, winw, winh, wind);
backBuffer = Gdk::Pixmap::create (window, winw, winh, -1);
Glib::RefPtr<Gdk::GC> bgc = Gdk::GC::create(backBuffer);
bgc->set_foreground (white);
backBuffer->draw_rectangle (bgc, true, 0, 0, winw, winh);
if (valid) {
// compute height of the full histogram (realheight) and
// does not take into account 0 and 255 values
// them are handled separately
int fullhistheight = 0;
for (int i=1; i<255; i++) {
if (needVal && lhist[i]>fullhistheight)
fullhistheight = lhist[i];
if (needRed && rhist[i]>fullhistheight)
fullhistheight = rhist[i];
if (needGreen && ghist[i]>fullhistheight)
fullhistheight = ghist[i];
if (needBlue && bhist[i]>fullhistheight)
fullhistheight = bhist[i];
}
int realhistheight = fullhistheight;
if (!showFull) {
int area1thres = 0;
int area2thres = 0;
int area = 0;
for (int i=0; i<fullhistheight; i++) {
for (int j=0; j<256; j++)
if ((needVal && lhist[j]>i) || (needRed && rhist[j]>i) || (needGreen && ghist[j]>i) || (needBlue && bhist[j]>i))
area++;
if (area1thres==0 && (double)area / (256*(i+1)) < 0.3)
area1thres = i;
if (area2thres==0 && (double)area / (256*(i+1)) < 0.3)
area2thres = i;
if (area1thres && area2thres)
break;
}
if (area1thres>0 && area2thres>0 && area1thres<fullhistheight)
realhistheight = area2thres;
}
if (realhistheight<winh-2)
realhistheight = winh-2;
Cairo::RefPtr<Cairo::Context> cr = backBuffer->create_cairo_context();
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
cr->set_line_width (1.0);
int ui = 0, oi = 0;
if (needVal) {
drawCurve(cr, lhist, realhistheight, winw, winh);
cr->set_source_rgb (0.75, 0.75, 0.75);
cr->fill_preserve ();
cr->set_source_rgb (0.5, 0.5, 0.5);
cr->stroke ();
drawMarks(cr, lhist, realhistheight, winw, ui, oi);
}
if (needRed) {
drawCurve(cr, rhist, realhistheight, winw, winh);
cr->set_source_rgb (1.0, 0.0, 0.0);
cr->stroke ();
drawMarks(cr, rhist, realhistheight, winw, ui, oi);
}
if (needGreen) {
drawCurve(cr, ghist, realhistheight, winw, winh);
cr->set_source_rgb (0.0, 1.0, 0.0);
cr->stroke ();
drawMarks(cr, ghist, realhistheight, winw, ui, oi);
}
if (needBlue) {
drawCurve(cr, bhist, realhistheight, winw, winh);
cr->set_source_rgb (0.0, 0.0, 1.0);
cr->stroke ();
drawMarks(cr, bhist, realhistheight, winw, ui, oi);
}
}
/*
// scale histogram to width winw-1
int* vval = new int[winw-1];
int* vred = new int[winw-1];
int* vgreen = new int[winw-1];
int* vblue = new int[winw-1];
memset (vval, 0, sizeof(int)*(winw-1));
memset (vred, 0, sizeof(int)*(winw-1));
memset (vgreen, 0, sizeof(int)*(winw-1));
memset (vblue, 0, sizeof(int)*(winw-1));
int index = 0;
double scale = 256.0 / (winw-2);
for (int i=0; i<=winw-2; i++) {
int samples = 0;
while (index < 256 && (int)(index/scale) == i) {
vval[i] += lhist[index];
vred[i] += rhist[index];
vgreen[i] += ghist[index];
vblue[i] += bhist[index];
index++;
samples++;
}
if (samples>0) {
vval[i] /= samples;
vred[i] /= samples;
vgreen[i] /= samples;
vblue[i] /= samples;
}
}
// compute height of the full histogram (realheight) and
int fullhistheight = 0;
for (int i=0; i<=winw-2; i++) {
if (needVal && vval[i]>fullhistheight)
fullhistheight = vval[i];
if (needRed && vred[i]>fullhistheight)
fullhistheight = vred[i];
if (needGreen && vgreen[i]>fullhistheight)
fullhistheight = vgreen[i];
if (needBlue && vblue[i]>fullhistheight)
fullhistheight = vblue[i];
}
// compute two hights, one for the magnified view and one for the threshold
int realhistheight = fullhistheight;
if (!showFull) {
int area1thres = 0;
int area2thres = 0;
int area = 0;
for (int i=0; i<fullhistheight; i++) {
for (int j=0; j<winw-1; j++)
if ((needVal && vval[j]>i) || (needRed && vred[j]>i) || (needGreen && vgreen[j]>i) || (needBlue && vblue[j]>i))
area++;
if (area1thres==0 && (double)area / ((winw-1)*(i+1)) < 0.3)
area1thres = i;
if (area2thres==0 && (double)area / ((winw-1)*(i+1)) < 0.3)
area2thres = i;
if (area1thres && area2thres)
break;
}
if (area1thres>0 && area2thres>0 && area1thres<fullhistheight)
realhistheight = area2thres;
}
if (realhistheight<winh-2)
realhistheight = winh-2;
Cairo::RefPtr<Cairo::Context> cr = backBuffer->create_cairo_context();
cr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
cr->set_line_width (1.0);
if (needVal) {
cr->move_to (0, winh-1);
cr->set_source_rgb (0.75, 0.75, 0.75);
for (int i=0; i<=winw-2; i++) {
int val = (int)(vval[i] * (double)(winh-2) / realhistheight);
if (val>winh-1)
val = winh-1;
if (i>0)
cr->line_to (i+1, winh-1-val);
}
cr->fill_preserve ();
cr->set_source_rgb (0.5, 0.5, 0.5);
cr->stroke ();
}
if (needRed) {
cr->move_to (0, winh-1);
cr->set_source_rgb (1.0, 0.0, 0.0);
for (int i=0; i<=winw-2; i++) {
int val = (int)(vred[i] * (double)(winh-2) / realhistheight);
if (val>winh-1)
val = winh-1;
if (i>0)
cr->line_to (i+1, winh-1-val);
}
cr->stroke ();
}
if (needGreen) {
cr->move_to (0, winh-1);
cr->set_source_rgb (0.0, 1.0, 0.0);
for (int i=0; i<=winw-2; i++) {
int val = (int)(vgreen[i] * (double)(winh-2) / realhistheight);
if (val>winh-1)
val = winh-1;
if (i>0)
cr->line_to (i+1, winh-1-val);
}
cr->stroke ();
}
if (needBlue) {
cr->move_to (0, winh-1);
cr->set_source_rgb (0.0, 0.0, 1.0);
for (int i=0; i<=winw-2; i++) {
int val = (int)(vblue[i] * (double)(winh-2) / realhistheight);
if (val>winh-1)
val = winh-1;
if (i>0)
cr->line_to (i+1, winh-1-val);
}
cr->stroke ();
}
delete [] vval;
delete [] vred;
delete [] vgreen;
delete [] vblue;
}
*/
bgc->set_foreground (mgray);
backBuffer->draw_rectangle (bgc, false, 0, 0, winw-1, winh-1);
bgc->set_line_attributes (1, Gdk::LINE_ON_OFF_DASH, Gdk::CAP_NOT_LAST, Gdk::JOIN_MITER);
backBuffer->draw_line (bgc, winw/3, 0, winw/3, winh-1);
backBuffer->draw_line (bgc, 2*winw/3, 0, 2*winw/3, winh-1);
backBuffer->draw_line (bgc, 0, winh/3, winw-1, winh/3);
backBuffer->draw_line (bgc, 0, 2*winh/3, winw-1, 2*winh/3);
bgc->set_line_attributes (1, Gdk::LINE_SOLID, Gdk::CAP_NOT_LAST, Gdk::JOIN_MITER);
oldwidth = winw;
oldheight = winh;
}
void HistogramArea::on_realize () {
Gtk::DrawingArea::on_realize();
Glib::RefPtr<Gdk::Window> window = get_window();
gc_ = Gdk::GC::create(window);
add_events(Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK);
Glib::RefPtr<Gdk::Colormap> colormap = get_default_colormap();
black = Gdk::Color ("black");
red = Gdk::Color ("red");
green = Gdk::Color ("green");
blue = Gdk::Color ("blue");
lgray = Gdk::Color ("gray75");
mgray = Gdk::Color ("gray50");
dgray = Gdk::Color ("gray25");
colormap->alloc_color(black);
colormap->alloc_color(white);
colormap->alloc_color(red);
colormap->alloc_color(green);
colormap->alloc_color(blue);
colormap->alloc_color(lgray);
colormap->alloc_color(mgray);
colormap->alloc_color(dgray);
}
void HistogramArea::drawCurve(Cairo::RefPtr<Cairo::Context> &cr,
unsigned int * data, double scale, int hsize, int vsize)
{
cr->move_to (0, vsize-1);
for (int i = 0; i < 256; i++) {
double val = data[i] * (double)(vsize-2) / scale;
if (val > vsize - 1)
val = vsize - 1;
cr->line_to ((i/255.0)*(hsize - 1), vsize - 1 - val);
}
cr->line_to (hsize - 1, vsize - 1);
}
void HistogramArea::drawMarks(Cairo::RefPtr<Cairo::Context> &cr,
unsigned int * data, double scale, int hsize, int & ui, int & oi)
{
int s = 8;
if(data[0] > scale) {
cr->rectangle(0, (ui++)*s, s, s);
}
if(data[255] > scale) {
cr->rectangle(hsize - s, (oi++)*s, s, s);
}
cr->fill();
}
void HistogramArea::styleChanged (const Glib::RefPtr<Gtk::Style>& style) {
white = get_style()->get_base(Gtk::STATE_NORMAL);
queue_draw ();
}
bool HistogramArea::on_expose_event(GdkEventExpose* event) {
Glib::RefPtr<Gdk::Window> window = get_window();
int winx, winy, winw, winh, wind;
window->get_geometry(winx, winy, winw, winh, wind);
if (winw!=oldwidth && winh!=oldheight)
renderHistogram ();
window->draw_drawable (gc_, backBuffer, 0, 0, 0, 0, -1, -1);
return true;
}
bool HistogramArea::on_button_press_event (GdkEventButton* event) {
if (event->type==GDK_2BUTTON_PRESS && event->button==1) {
showFull = !showFull;
renderHistogram ();
queue_draw ();
}
return true;
}