Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_hsv_adjustment_filter.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2022 Sam Linnfer <littlelightlittlefire@gmail.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include <array>
9
11
12#include <klocalizedstring.h>
13
16#include <kis_selection.h>
17#include <kis_paint_device.h>
19#include <KoColorSpace.h>
20#include <KoColorProfile.h>
23#include <KoColorConversions.h>
25#include <KisHsvColorSlider.h>
26
27#include "kis_signals_blocker.h"
28
29namespace {
30
31enum class SLIDER_TYPE {
32 HUE,
33 SATURATION,
34 VALUE,
35 LIGHTNESS,
36 LUMA,
37 INTENSITY,
38 YELLOW_BLUE,
39 GREEN_RED,
40 LUMA_YUV,
41};
42
43// Corresponds the 5 transformations defined in kis_hsv_adjustment.cpp
44// 0:HSV, 1:HSL, 2:HSI, 3:HSY, 4:YUV
45enum class SLIDER_SET {
46 HSV, HSL, HSI, HSY, YUV,
47};
48
49struct SliderSettings {
50 SliderSettings(SLIDER_TYPE type, KisHsvColorSlider::MIX_MODE mixMode, const KLocalizedString &title, int min, int max, int minRelative, int maxRelative, int resetValue)
51 : m_type(type)
52 , m_mixMode(mixMode)
53 , m_title(title)
54 , m_min(min)
55 , m_max(max)
56 , m_minRelative(minRelative)
57 , m_maxRelative(maxRelative)
58 , m_resetValue(resetValue)
59 {
60 }
61
62 void apply(QLabel *label, KisHsvColorSlider *slider, QSpinBox *spinBox, bool prevColorize, bool colorize) const {
63 int value = slider->value();
64 const double norm = normalize(prevColorize, value);
65 const int min = colorize ? m_min : m_minRelative;
66 const int max = colorize ? m_max : m_maxRelative;
67
68 label->setText(m_title.toString());
69 slider->setMinimum(min);
70 slider->setMaximum(max);
71 spinBox->setMinimum(min);
72 spinBox->setMaximum(max);
73
74 if (prevColorize != colorize) {
75 // Rescale the value
76 value = static_cast<int>(min + norm * static_cast<double>(max - min));
77 }
78
79 value = qBound(min, value, max);
80 spinBox->setValue(value);
81 slider->setValue(value);
82 }
83
84 void recolor(KisHsvColorSlider *slider, SLIDER_SET set, bool colorize, qreal nHue, qreal nSaturation, qreal nValue) {
85 slider->setMixMode(m_mixMode);
86 SLIDER_TYPE type = m_type;
87
88 if (!colorize) {
89 switch (m_type) {
90 case SLIDER_TYPE::HUE:
91 // Slider colors shift to mimic hue changes on canvas.
92 nHue = fmod(nHue + 0.5, 1);
93 break;
94 case SLIDER_TYPE::GREEN_RED: // fallthrough
95 case SLIDER_TYPE::YELLOW_BLUE:
96 nHue = 0.5;
97 nSaturation = 0.5;
98 nValue = 0.5;
99 break;
100
101 default:
102 nHue = 0;
103 nSaturation = 0;
104 nValue = 1;
105
106 // Display a gray bar for YUV Luma in non-colorize mode
107 if (m_type == SLIDER_TYPE::LUMA_YUV) {
108 type = SLIDER_TYPE::LUMA;
109 }
110 }
111 }
112
113 switch (type) {
114 case SLIDER_TYPE::HUE: {
115 // The hue slider does not move on colorize mode
116 if (colorize) {
117 nHue = 0;
118 }
119 slider->setColors(nHue, 1, 1, nHue, 1, 1);
120 slider->setCircularHue(true);
121 break;
122 }
123
124 case SLIDER_TYPE::SATURATION: {
125 if (colorize) {
126 switch (set) {
127 case SLIDER_SET::HSY:
129 break;
130
131 case SLIDER_SET::HSI:
133 break;
134
135 case SLIDER_SET::HSL:
137 break;
138
139 default: // fallthrough
140 case SLIDER_SET::HSV:
141 break;
142 }
143 }
144
145 slider->setColors(nHue, 0, nValue, nHue, 1, nValue);
146 break;
147 }
148
149 case SLIDER_TYPE::VALUE: // fallthrough
150 case SLIDER_TYPE::LIGHTNESS: // fallthrough
151 case SLIDER_TYPE::LUMA: // fallthrough
152 case SLIDER_TYPE::INTENSITY: {
153 slider->setColors(nHue, nSaturation, 0, nHue, nSaturation, 1);
154 break;
155 }
156
157 case SLIDER_TYPE::GREEN_RED:
158 case SLIDER_TYPE::YELLOW_BLUE:
159 case SLIDER_TYPE::LUMA_YUV: {
161
162 qreal minR, minG, minB;
163 qreal maxR, maxG, maxB;
164
165 if (m_type == SLIDER_TYPE::GREEN_RED) { // Cr
166 YUVToRGB(nValue, nHue, 0, &minR, &minG, &minB);
167 YUVToRGB(nValue, nHue, 1, &maxR, &maxG, &maxB);
168 } else if (m_type == SLIDER_TYPE::YELLOW_BLUE) { // Cb
169 YUVToRGB(nValue, 0, nSaturation, &minR, &minG, &minB);
170 YUVToRGB(nValue, 1, nSaturation, &maxR, &maxG, &maxB);
171 } else { // Y
172 YUVToRGB(0, nHue, nSaturation, &minR, &minG, &minB);
173 YUVToRGB(1, nHue, nSaturation, &maxR, &maxG, &maxB);
174 }
175
176 // Clamp
177 minR = qBound(0.0, minR, 1.0);
178 minG = qBound(0.0, minG, 1.0);
179 minB = qBound(0.0, minB, 1.0);
180
181 maxR = qBound(0.0, maxR, 1.0);
182 maxG = qBound(0.0, maxG, 1.0);
183 maxB = qBound(0.0, maxB, 1.0);
184
185 QColor minC, maxC;
186 minC.setRgbF(minR, minG, minB);
187 maxC.setRgbF(maxR, maxG, maxB);
188
189 KoColor minK(minC, cs);
190 KoColor maxK(maxC, cs);
191
192 slider->setColors(minK, maxK);
193 break;
194 }
195 }
196 }
197
198 void reset(KisHsvColorSlider *slider) const {
199 slider->setValue(m_resetValue);
200 }
201
202 double scale(bool colorize, double value) const {
203 if (colorize) {
204 return value / m_max;
205 }
206 return value / m_maxRelative;
207 }
208
209 double normalize(bool colorize, double value) const {
210 if (colorize) {
211 return (value - m_min) / (m_max - m_min);
212 }
213 return (value - m_minRelative) / (m_maxRelative - m_minRelative);
214 }
215
216 SLIDER_TYPE m_type;
218 KLocalizedString m_title;
219 int m_min, m_max;
220 int m_minRelative, m_maxRelative;
221 int m_resetValue;
222};
223
224// Slider configuration based on their SLIDER_TYPE
225const SliderSettings SLIDER_TABLE[9] = {
226 SliderSettings(SLIDER_TYPE::HUE, KisHsvColorSlider::MIX_MODE::HSV, ki18n("Hue"), 0, 360, -180, 180, 0),
227 SliderSettings(SLIDER_TYPE::SATURATION, KisHsvColorSlider::MIX_MODE::HSV, ki18n("Saturation"), 0, 100, -100, 100, 0),
228 SliderSettings(SLIDER_TYPE::VALUE, KisHsvColorSlider::MIX_MODE::HSV, ki18nc("Brightness level of HSV model", "Value"), -100, 100, -100, 100, 0),
229 SliderSettings(SLIDER_TYPE::LIGHTNESS, KisHsvColorSlider::MIX_MODE::HSL, ki18n("Lightness"), -100, 100, -100, 100, 0),
230 SliderSettings(SLIDER_TYPE::LUMA, KisHsvColorSlider::MIX_MODE::HSY, ki18n("Luma"), -100, 100, -100, 100, 0),
231 SliderSettings(SLIDER_TYPE::INTENSITY, KisHsvColorSlider::MIX_MODE::HSI, ki18nc("Brightness in HSI color model", "Intensity"), -100, 100, -100, 100, 0),
232 SliderSettings(SLIDER_TYPE::YELLOW_BLUE,KisHsvColorSlider::MIX_MODE::COLOR_SPACE, ki18n("Yellow-Blue"), 0, 100, -100, 100, 0),
233 SliderSettings(SLIDER_TYPE::GREEN_RED, KisHsvColorSlider::MIX_MODE::COLOR_SPACE, ki18n("Green-Red"), 0, 100, -100, 100, 0),
234 SliderSettings(SLIDER_TYPE::LUMA_YUV, KisHsvColorSlider::MIX_MODE::COLOR_SPACE, ki18n("Luma"), -100, 100, -100, 100, 0),
235};
236
237// Defines which sliders to display in each set.
238// One for each SLIDER_SET.
239const std::array<SLIDER_TYPE, 3> SLIDER_SETS[5] = {
240 { SLIDER_TYPE::HUE, SLIDER_TYPE::SATURATION, SLIDER_TYPE::VALUE },
241 { SLIDER_TYPE::HUE, SLIDER_TYPE::SATURATION, SLIDER_TYPE::LIGHTNESS },
242 { SLIDER_TYPE::HUE, SLIDER_TYPE::SATURATION, SLIDER_TYPE::INTENSITY },
243 { SLIDER_TYPE::HUE, SLIDER_TYPE::SATURATION, SLIDER_TYPE::LUMA },
244 { SLIDER_TYPE::YELLOW_BLUE, SLIDER_TYPE::GREEN_RED, SLIDER_TYPE::LUMA_YUV },
245};
246
247SliderSettings sliderSetting(SLIDER_TYPE type) {
248 return SLIDER_TABLE[static_cast<int>(type)];
249}
250
251}
252
254 : KisColorTransformationFilter(id(), FiltersCategoryAdjustId, i18n("&HSV Adjustment..."))
255{
256 setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U));
258}
259
261{
262 Q_UNUSED(dev);
263 return new KisHSVConfigWidget(parent);
264}
265
267{
268 QHash<QString, QVariant> params;
269 if (config) {
270 int type = config->getInt("type", 1);
271 bool colorize = config->getBool("colorize", false);
272 bool compatibilityMode = config->getBool("compatibilityMode", true);
273
274 const std::array<SLIDER_TYPE, 3> sliderSet = SLIDER_SETS[static_cast<int>(type)];
275
276 params["h"] = sliderSetting(sliderSet[0]).scale(colorize, config->getInt("h", 0));
277 params["s"] = sliderSetting(sliderSet[1]).scale(colorize, config->getInt("s", 0));
278 params["v"] = sliderSetting(sliderSet[2]).scale(colorize, config->getInt("v", 0));
279
280 params["type"] = type;
281 params["colorize"] = colorize;
282 params["lumaRed"] = cs->lumaCoefficients()[0];
283 params["lumaGreen"] = cs->lumaCoefficients()[1];
284 params["lumaBlue"] = cs->lumaCoefficients()[2];
285 params["compatibilityMode"] = compatibilityMode;
286 }
287 return cs->createColorTransformation("hsv_adjustment", params);
288}
289
291{
292 KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
293 config->setProperty("h", 0);
294 config->setProperty("s", 0);
295 config->setProperty("v", 0);
296 config->setProperty("type", 1);
297 config->setProperty("colorize", false);
298 config->setProperty("compatibilityMode", false);
299 return config;
300}
301
302KisHSVConfigWidget::KisHSVConfigWidget(QWidget *parent, Qt::WindowFlags f)
303 : KisConfigWidget(parent, f)
304 , m_prevColorize(false)
305{
306 m_page = new Ui_WdgHSVAdjustment();
307 m_page->setupUi(this);
308
309 connect(m_page->cmbType, SIGNAL(activated(int)), this, SLOT(configureSliderLimitsAndLabels()));
310 connect(m_page->chkColorize, SIGNAL(toggled(bool)), this, SLOT(configureSliderLimitsAndLabels()));
311 connect(m_page->chkCompatibilityMode, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
312 connect(m_page->reset, SIGNAL(clicked(bool)), this, SLOT(resetFilter()));
313
314 // connect horizontal sliders
315 connect(m_page->hSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
316 connect(m_page->sSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
317 connect(m_page->vSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
318
319 connect(m_page->hSpinBox, SIGNAL(valueChanged(int)), m_page->hSlider, SLOT(setValue(int)));
320 connect(m_page->sSpinBox, SIGNAL(valueChanged(int)), m_page->sSlider, SLOT(setValue(int)));
321 connect(m_page->vSpinBox, SIGNAL(valueChanged(int)), m_page->vSlider, SLOT(setValue(int)));
322
323 connect(m_page->hSlider, SIGNAL(valueChanged(int)), m_page->hSpinBox, SLOT(setValue(int)));
324 connect(m_page->sSlider, SIGNAL(valueChanged(int)), m_page->sSpinBox, SLOT(setValue(int)));
325 connect(m_page->vSlider, SIGNAL(valueChanged(int)), m_page->vSpinBox, SLOT(setValue(int)));
326
327 connect(m_page->hSlider, SIGNAL(valueChanged(int)), this, SLOT(recolorSliders()));
328 connect(m_page->sSlider, SIGNAL(valueChanged(int)), this, SLOT(recolorSliders()));
329 connect(m_page->vSlider, SIGNAL(valueChanged(int)), this, SLOT(recolorSliders()));
330}
331
336
338{
340 c->setProperty("h", m_page->hSlider->value());
341 c->setProperty("s", m_page->sSlider->value());
342 c->setProperty("v", m_page->vSlider->value());
343 c->setProperty("type", m_page->cmbType->currentIndex());
344 c->setProperty("colorize", m_page->chkColorize->isChecked());
345 c->setProperty("compatibilityMode", m_page->chkCompatibilityMode->isChecked());
346 return c;
347}
348
350{
351 const int type = config->getInt("type", 1);
352 m_page->cmbType->setCurrentIndex(type);
353 m_page->chkColorize->setChecked(config->getBool("colorize", false));
354 m_page->hSlider->setValue(config->getInt("h", 0));
355 m_page->sSlider->setValue(config->getInt("s", 0));
356 m_page->vSlider->setValue(config->getInt("v", 0));
357 m_page->chkCompatibilityMode->setChecked(config->getInt("compatibilityMode", true));
358
360}
361
363{
364 KIS_SAFE_ASSERT_RECOVER_RETURN(m_page->cmbType->currentIndex() >= 0);
365
366 const std::array<SLIDER_TYPE, 3> sliderSet = SLIDER_SETS[m_page->cmbType->currentIndex()];
367 const bool colorize = m_page->chkColorize->isChecked();
368
369 // Prevent the sliders from recoloring when setValue is triggered in apply()
370 // Since some values will be invalid while inside this function if colorize is changed
371 KisSignalsBlocker blocker(m_page->hSlider, m_page->sSlider, m_page->vSlider);
372
373 sliderSetting(sliderSet[0]).apply(m_page->hLabel, m_page->hSlider, m_page->hSpinBox, m_prevColorize, colorize);
374 sliderSetting(sliderSet[1]).apply(m_page->sLabel, m_page->sSlider, m_page->sSpinBox, m_prevColorize, colorize);
375 sliderSetting(sliderSet[2]).apply(m_page->vLabel, m_page->vSlider, m_page->vSpinBox, m_prevColorize, colorize);
376
378
379 const bool compat = !m_page->chkColorize->isChecked() && m_page->cmbType->currentIndex() <= 3;
380
381 m_page->chkCompatibilityMode->setEnabled(compat);
382 m_prevColorize = colorize;
383
385}
386
388{
389 const SLIDER_SET set = static_cast<SLIDER_SET>(m_page->cmbType->currentIndex());
390 const std::array<SLIDER_TYPE, 3> sliderSet = SLIDER_SETS[m_page->cmbType->currentIndex()];
391 const bool colorize = m_page->chkColorize->isChecked();
392
393 const double nh = sliderSetting(sliderSet[0]).normalize(colorize, m_page->hSlider->value());
394 const double ns = sliderSetting(sliderSet[1]).normalize(colorize, m_page->sSlider->value());
395 const double nv = sliderSetting(sliderSet[2]).normalize(colorize, m_page->vSlider->value());
396
397 sliderSetting(sliderSet[0]).recolor(m_page->hSlider, set, colorize, nh, ns, nv);
398 sliderSetting(sliderSet[1]).recolor(m_page->sSlider, set, colorize, nh, ns, nv);
399 sliderSetting(sliderSet[2]).recolor(m_page->vSlider, set, colorize, nh, ns, nv);
400}
401
403{
404 const std::array<SLIDER_TYPE, 3> sliderSet = SLIDER_SETS[m_page->cmbType->currentIndex()];
405
406 sliderSetting(sliderSet[0]).reset(m_page->hSlider);
407 sliderSetting(sliderSet[1]).reset(m_page->sSlider);
408 sliderSetting(sliderSet[2]).reset(m_page->vSlider);
409}
410
float value(const T *src, size_t ch)
void YUVToRGB(const qreal y, const qreal u, const qreal v, qreal *r, qreal *g, qreal *b, qreal R, qreal G, qreal B)
const KoID YCbCrAColorModelID("YCbCrA", ki18n("YCbCr/Alpha"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
void sigConfigurationItemChanged()
static KisResourcesInterfaceSP instance()
KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
KoColorTransformation * createTransformation(const KoColorSpace *cs, const KisFilterConfigurationSP config) const override
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
KisPropertiesConfigurationSP configuration() const override
KisHSVConfigWidget(QWidget *parent, Qt::WindowFlags f=Qt::WindowFlags())
void setConfiguration(const KisPropertiesConfigurationSP config) override
Ui_WdgHSVAdjustment * m_page
QVector< qreal > lumaCoefficients
KoColorTransformation * createColorTransformation(const QString &id, const QHash< QString, QVariant > &parameters) const
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
const KoID FiltersCategoryAdjustId("adjust_filters", ki18nc("The category of color adjustment filters, like levels. Verb.", "Adjust"))
T min(T a, T b, T c)
Point normalize(const Point &a)
qreal norm(const T &a)
constexpr std::enable_if< sizeof...(values)==0, size_t >::type max()
Definition flatten.c:229
void setShortcut(const QKeySequence &shortcut)
void setSupportsPainting(bool v)
void setColors(const KoColor minColor, const KoColor maxColor)
void setMixMode(MIX_MODE mode)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()