rawTherapee/rtgui/lockablecolorpicker.cc
Lawrence Lee 8accebe416
Fix incorrect sampled L*a*b* values
Use LCMS to convert values back into L*a*b*. The pipette buffer has the
output or working profile applied with LCMS. Performing the inverse
operation fixes the incorrect values shown in the navigator, histogram
indicator bars, and lockable color pickers.
2023-05-28 18:15:27 -07:00

403 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 <https://www.gnu.org/licenses/>.
*/
#include "lockablecolorpicker.h"
#include "options.h"
#include "../rtengine/color.h"
#include "../rtengine/improcfun.h"
#include "../rtengine/rt_math.h"
#include "../rtengine/utils.h"
#include "imagearea.h"
#include "multilangmgr.h"
#include "navigator.h"
namespace
{
const rtengine::procparams::ColorManagementParams DEFAULT_CMP;
}
LockableColorPicker::LockableColorPicker (CropWindow* cropWindow, rtengine::procparams::ColorManagementParams *color_management_params)
: cropWindow(cropWindow), displayedValues(ColorPickerType::RGB), position(0, 0), size(Size::S15),
color_management_params(color_management_params), validity(Validity::OUTSIDE),
r(0.f), g(0.f), b(0.f), rpreview(0.f), gpreview(0.f), bpreview(0.f), hue(0.f), sat(0.f), val(0.f), L(0.f), a(0.f), bb(0.f)
{}
void LockableColorPicker::updateBackBuffer ()
{
int newW, newH;
// -------------------- setting some key constants ---------------------
constexpr double circlePadding = 3.0; // keep this value odd
constexpr double opacity = 0.62;
// ---------------------------------------------------------------------
if (validity == Validity::INSIDE) {
Gtk::DrawingArea *iArea = cropWindow->getImageArea();
Glib::RefPtr<Pango::Context> pangoContext = iArea->get_pango_context ();
Pango::FontDescription fontd = pangoContext->get_font_description();
// set font family and size
fontd.set_family(options.CPFontFamily == "default" ? "sans" : options.CPFontFamily);
fontd.set_size((options.CPFontFamily == "default" ? 8 : options.CPFontSize) * Pango::SCALE);
fontd.set_weight(Pango::WEIGHT_NORMAL);
pangoContext->set_font_description (fontd);
Glib::RefPtr<Pango::Layout> layout[3][2];
Glib::ustring s1, s2, s3;
PointerMotionListener* navigator = cropWindow->getPointerMotionListener ();
switch (displayedValues) {
case ColorPickerType::RGB:
navigator->getRGBText ((int)(r*255.f), (int)(g*255.f), (int)(b*255.f), s1, s2, s3);
layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_R"));
layout[0][1] = iArea->create_pango_layout(s1);
layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_G"));
layout[1][1] = iArea->create_pango_layout(s2);
layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_B"));
layout[2][1] = iArea->create_pango_layout(s3);
break;
case ColorPickerType::HSV:
navigator->getHSVText (hue, sat, val, s1, s2, s3);
layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_H"));
layout[0][1] = iArea->create_pango_layout(s1);
layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_S"));
layout[1][1] = iArea->create_pango_layout(s2);
layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_V"));
layout[2][1] = iArea->create_pango_layout(s3);
break;
case ColorPickerType::LAB:
default:
navigator->getLABText (L, a, bb, s1, s2, s3);
layout[0][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_L"));
layout[0][1] = iArea->create_pango_layout(s1);
layout[1][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_A"));
layout[1][1] = iArea->create_pango_layout(s2);
layout[2][0] = iArea->create_pango_layout(M("NAVIGATOR_LAB_B"));
layout[2][1] = iArea->create_pango_layout(s3);
}
int w00, w01, w10, w11, w20, w21, h00, h01, h10, h11, h20, h21;
layout[0][0]->get_pixel_size(w00, h00);
layout[1][0]->get_pixel_size(w10, h10);
layout[2][0]->get_pixel_size(w20, h20);
layout[0][1]->get_pixel_size(w01, h01);
layout[1][1]->get_pixel_size(w11, h11);
layout[2][1]->get_pixel_size(w21, h21);
int maxWCol0 = rtengine::max(w00, w10, w20);
int maxWCol1 = rtengine::max(w01, w11, w21);
int maxHRow0 = rtengine::max(h00, h01);
int maxHRow1 = rtengine::max(h10, h11);
int maxHRow2 = rtengine::max(h20, h21);
// -------------------- setting some key constants ---------------------
constexpr int textPadding = 3;
const int textWidth = maxWCol0 + maxWCol1 + textPadding;
const int textHeight = maxHRow0 + maxHRow1 + maxHRow2 + 2*textPadding;
// ---------------------------------------------------------------------
newW = rtengine::max<int>((int)size + 2 * circlePadding, textWidth + 2 * textPadding);
newH = (int)size + 2 * circlePadding + textHeight + 2 * textPadding;
setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, newW, newH, true);
Cairo::RefPtr<Cairo::Context> bbcr = BackBuffer::getContext();
// cleaning the back buffer
bbcr->set_source_rgba (0., 0., 0., 0.);
bbcr->set_operator (Cairo::OPERATOR_CLEAR);
bbcr->paint ();
bbcr->set_operator (Cairo::OPERATOR_OVER);
bbcr->set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
bbcr->set_line_width (0.);
double center = static_cast<double>(size) / 2.0 + circlePadding;
// black background of the whole color picker
bbcr->set_line_width (0.);
bbcr->set_source_rgba (0., 0., 0., opacity);
bbcr->arc_negative (center, center, center, 0., rtengine::RT_PI);
bbcr->line_to (0, 2. * center + textHeight);
bbcr->arc_negative (2. * textPadding, 2. * center + textHeight, 2. * textPadding, rtengine::RT_PI, rtengine::RT_PI / 2.);
bbcr->line_to (textWidth, 2. * center + textHeight + 2. * textPadding);
bbcr->arc_negative (textWidth, 2. * center + textHeight, 2. * textPadding, rtengine::RT_PI / 2., 0.);
bbcr->line_to (textWidth + 2. * textPadding, 2. * center + 2. * textPadding);
bbcr->arc_negative (textWidth, 2. * center + 2. * textPadding, 2. * textPadding, 0., rtengine::RT_PI * 1.5);
bbcr->line_to (2. * center, 2. * center);
bbcr->close_path();
bbcr->set_line_join (Cairo::LINE_JOIN_BEVEL);
bbcr->set_line_cap (Cairo::LINE_CAP_SQUARE);
bbcr->fill ();
// light grey circle around the color mark
bbcr->arc (center, center, center - circlePadding / 2., 0., 2. * (double)rtengine::RT_PI);
bbcr->set_source_rgb (0.75, 0.75, 0.75);
bbcr->set_line_width (circlePadding - 2.);
bbcr->stroke ();
// spot disc with picked color
bbcr->arc (center, center, center - circlePadding, 0., 2. * (double)rtengine::RT_PI);
bbcr->set_source_rgb (rpreview, gpreview, bpreview); // <- set the picker color here
bbcr->set_line_width (0.);
bbcr->fill();
// adding the font
bbcr->set_line_width (0.);
bbcr->set_line_join (Cairo::LINE_JOIN_ROUND);
bbcr->set_line_cap (Cairo::LINE_CAP_ROUND);
bbcr->set_source_rgb (1., 1., 1.);
double txtOffsetX = textPadding;
double txtOffsetY = (double)size + 2. * circlePadding + textPadding;
switch (iArea->get_direction()) {
case Gtk::TEXT_DIR_RTL:
bbcr->move_to (txtOffsetX , txtOffsetY);
layout[0][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY);
layout[0][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX , txtOffsetY + maxHRow0 + textPadding);
layout[1][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY + maxHRow0 + textPadding);
layout[1][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX , txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
layout[2][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol1 + textPadding, txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
layout[2][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
break;
default:
bbcr->move_to (txtOffsetX , txtOffsetY);
layout[0][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY);
layout[0][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX , txtOffsetY + maxHRow0 + textPadding);
layout[1][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY + maxHRow0 + textPadding);
layout[1][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX , txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
layout[2][0]->add_to_cairo_context (bbcr);
bbcr->fill ();
bbcr->move_to (txtOffsetX + maxWCol0 + textPadding, txtOffsetY + maxHRow0 + maxHRow1 + 2*textPadding);
layout[2][1]->add_to_cairo_context (bbcr);
bbcr->fill ();
}
anchorOffset.set (center, center);
setDirty (false);
} else if (validity == Validity::CROSSING) {
newH = newW = (int)size + 2 * circlePadding;
setDrawRectangle(Cairo::FORMAT_ARGB32, 0, 0, newW, newH, true);
Cairo::RefPtr<Cairo::Context> bbcr = BackBuffer::getContext();
// cleaning the back buffer
bbcr->set_source_rgba (0., 0., 0., 0.);
bbcr->set_operator (Cairo::OPERATOR_CLEAR);
bbcr->paint ();
bbcr->set_operator (Cairo::OPERATOR_OVER);
bbcr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
double center = static_cast<double>(size) / 2. + circlePadding;
// light grey circle around the color mark
bbcr->arc (center, center, center - circlePadding / 2., 0., 2. * (double)rtengine::RT_PI);
bbcr->set_source_rgba (0., 0., 0., opacity);
bbcr->set_line_width(circlePadding);
bbcr->stroke_preserve();
bbcr->set_source_rgb (0.75, 0.75, 0.75);
bbcr->set_line_width (circlePadding - 2.);
bbcr->stroke ();
anchorOffset.set (center, center);
setDirty (false);
}
}
void LockableColorPicker::draw (const Cairo::RefPtr<Cairo::Context> &cr)
{
if (validity == Validity::OUTSIDE) {
return;
}
if (isDirty()) {
updateBackBuffer();
}
int px, py;
cropWindow->imageCoordToScreen(position.x, position.y, px, py);
setDestPosition(px - anchorOffset.x, py - anchorOffset.y);
copySurface(cr);
}
void LockableColorPicker::setPosition (const rtengine::Coord &newPos)
{
// we're not checking bounds here, this will be done at rendering time
position = newPos;
}
void LockableColorPicker::setRGB (const float R, const float G, const float B, const float previewR, const float previewG, const float previewB)
{
if (r==R && g==G && b==B) {
return;
}
r = R;
g = G;
b = B;
rpreview = previewR;
gpreview = previewG;
bpreview = previewB;
rtengine::Color::rgb2hsv01(r, g, b, hue, sat, val);
rtengine::ImProcFunctions::rgb2lab(
static_cast<std::uint8_t>(255 * r),
static_cast<std::uint8_t>(255 * g),
static_cast<std::uint8_t>(255 * b),
L, a, bb,
color_management_params != nullptr ? *color_management_params : DEFAULT_CMP,
true);
L /= 327.68f;
a /= 327.68f;
bb /= 327.68f;
if (validity != Validity::OUTSIDE) {
setDirty(true);
}
}
void LockableColorPicker::getImagePosition (rtengine::Coord &imgPos) const
{
imgPos = position;
}
void LockableColorPicker::getScreenPosition (rtengine::Coord &screenPos) const
{
if (cropWindow) {
cropWindow->imageCoordToScreen(position.x, position.y, screenPos.x, screenPos.y);
}
}
bool LockableColorPicker::isOver (int x, int y)
{
if (!cropWindow) {
return false;
}
rtengine::Coord pickerScreenPos;
cropWindow->imageCoordToScreen(position.x, position.y, pickerScreenPos.x, pickerScreenPos.y);
rtengine::Coord mousePos(x, y);
rtengine::Coord wh(getWidth(), getHeight());
rtengine::Coord tl(pickerScreenPos - anchorOffset);
rtengine::Coord br(tl + wh);
return mousePos >= tl && mousePos <= br;
}
void LockableColorPicker::setValidity (Validity validity)
{
if (this->validity != validity) {
setDirty(true);
}
this->validity = validity;
}
void LockableColorPicker::setSize (Size newSize)
{
if (size != newSize)
{
size = newSize;
setDirty(true);
}
}
LockableColorPicker::Size LockableColorPicker::getSize () const
{
return size;
}
void LockableColorPicker::rollDisplayedValues ()
{
if (displayedValues < ColorPickerType::LAB) {
displayedValues = (ColorPickerType)(rtengine::toUnderlying(displayedValues) + 1);
} else {
displayedValues = ColorPickerType::RGB;
}
setDirty(true);
}
bool LockableColorPicker::incSize ()
{
if (size < Size::S30) {
size = (Size)(rtengine::toUnderlying(size) + 5);
setDirty(true);
return true;
}
return false;
}
bool LockableColorPicker::decSize ()
{
if (size > Size::S5) {
size = (Size)(rtengine::toUnderlying(size) - 5);
setDirty(true);
return true;
}
return false;
}
// return true if the picker has to be redrawn
bool LockableColorPicker::cycleRGB ()
{
if (displayedValues == ColorPickerType::RGB) {
setDirty (true);
return true;
}
return false;
}
// return true if the picker has to be redrawn
bool LockableColorPicker::cycleHSV ()
{
if (displayedValues == ColorPickerType::HSV) {
setDirty (true);
return true;
}
return false;
}