Krita Source Code Documentation
Loading...
Searching...
No Matches
KisLevelsConfigWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2006 Frederic Coiffier <fcoiffie@gmail.com>
5 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10#include <cmath>
11
12#include <klocalizedstring.h>
13
14#include <QtGlobal>
15#include <QPixmap>
16#include <QSpinBox>
17#include <QMessageBox>
18#include <QEvent>
19#include <QPainter>
20#include <QCheckBox>
21
23#include <kis_paint_device.h>
24#include <kis_histogram.h>
25#include <kis_painter.h>
27#include <kis_selection.h>
28#include <kis_types.h>
31#include <KoColorSpaceMaths.h>
32#include <kis_signals_blocker.h>
33#include <kis_icon_utils.h>
35#include <kis_painting_tweaks.h>
36#include <KoDialog.h>
37#include <KisAutoLevels.h>
38#include <KisAutoLevelsWidget.h>
39
41
43#include "KisLevelsFilter.h"
44
45static int deNormalizeValue(qreal value, int min, int max)
46{
47 return min + static_cast<int>(qRound(static_cast<qreal>(max - min) * value));
48}
49
50static qreal normalizeValue(int value, int min, int max)
51{
52 return static_cast<qreal>(value - min) / static_cast<qreal>(max - min);
53}
54
56 : KisConfigWidget(parent)
57 , m_dev(dev)
58 , m_colorSpace(colorSpace)
59 , m_activeChannel(0)
60 , m_activeLevelsCurve(nullptr)
61 , m_channelsHistogram(nullptr)
62 , m_lightnessHistogram(nullptr)
63{
64 Q_ASSERT(m_dev);
65 Q_ASSERT(m_colorSpace);
66
69
70 m_page.setupUi(this);
71 // In some styles the combo box is higher than the tool buttons so
72 // hiding/unhiding it can make the layout jump in that area.
73 // We set the spacer item height to the same as the combo box
74 m_page.spacer01->changeSize(0, m_page.comboBoxChannel->sizeHint().height(),
75 QSizePolicy::Expanding, QSizePolicy::Fixed);
76
77 // Only enable the "all-channels auto levels" button if the color space
78 // is rgb
79 m_page.buttonAutoLevelsAllChannels->setEnabled(m_colorSpace->colorModelId() == RGBAColorModelID);
80
82
84 for (int i = 0; i < m_virtualChannels.size(); ++i) {
85 m_levelsCurves.append(defaultLevelsCurve);
86 m_levelsCurves[i].setName(m_virtualChannels[i].name());
87 m_page.comboBoxChannel->addItem(m_virtualChannels[i].name(), i);
88 }
89 m_lightnessLevelsCurve.setName(i18nc("Lightness value in Lab color model", "Lightness"));
90
94
97
98 connect(m_page.buttonGroupMode, SIGNAL(buttonToggled(QAbstractButton*, bool)), SLOT(slot_buttonGroupMode_buttonToggled(QAbstractButton*, bool)));
99 connect(m_page.comboBoxChannel, SIGNAL(activated(int)), SLOT(slot_comboBoxChannel_activated(int)));
100 connect(m_page.buttonGroupHistogramMode, SIGNAL(buttonToggled(QAbstractButton*, bool)), SLOT(slot_buttonGroupHistogramMode_buttonToggled(QAbstractButton*, bool)));
101 connect(m_page.buttonScaleHistogramToFit, SIGNAL(clicked()), m_page.widgetHistogram, SLOT(setScaleToFit()));
102 connect(m_page.buttonScaleHistogramToCutLongPeaks, SIGNAL(clicked()), m_page.widgetHistogram, SLOT(setScaleToCutLongPeaks()));
103 connect(m_page.buttonResetAll, SIGNAL(clicked()), SLOT(resetAll()));
104 connect(m_page.buttonResetInputLevels, SIGNAL(clicked()), SLOT(resetInputLevels()));
105 connect(m_page.buttonResetOutputLevels, SIGNAL(clicked()), SLOT(resetOutputLevels()));
106 connect(m_page.buttonResetAllChannels, SIGNAL(clicked()), SLOT(resetAllChannels()));
107 connect(m_page.spinBoxInputBlackPoint, SIGNAL(valueChanged(int)), SLOT(slot_spinBoxInputBlackPoint_valueChanged(int)));
108 connect(m_page.spinBoxInputWhitePoint, SIGNAL(valueChanged(int)), SLOT(slot_spinBoxInputWhitePoint_valueChanged(int)));
109 connect(m_page.spinBoxInputGamma, SIGNAL(valueChanged(qreal)), SLOT(slot_spinBoxInputGamma_valueChanged(qreal)));
110 connect(m_page.spinBoxOutputBlackPoint, SIGNAL(valueChanged(int)), SLOT(slot_spinBoxOutputBlackPoint_valueChanged(int)));
111 connect(m_page.spinBoxOutputWhitePoint, SIGNAL(valueChanged(int)), SLOT(slot_spinBoxOutputWhitePoint_valueChanged(int)));
112 connect(m_page.sliderInputLevels, SIGNAL(blackPointChanged(qreal)), SLOT(slot_sliderInputLevels_blackPointChanged(qreal)));
113 connect(m_page.sliderInputLevels, SIGNAL(whitePointChanged(qreal)), SLOT(slot_sliderInputLevels_whitePointChanged(qreal)));
114 connect(m_page.sliderInputLevels, SIGNAL(gammaChanged(qreal)), SLOT(slot_sliderInputLevels_gammaChanged(qreal)));
115 connect(m_page.sliderOutputLevels, SIGNAL(blackPointChanged(qreal)), SLOT(slot_sliderOutputLevels_blackPointChanged(qreal)));
116 connect(m_page.sliderOutputLevels, SIGNAL(whitePointChanged(qreal)), SLOT(slot_sliderOutputLevels_whitePointChanged(qreal)));
117 connect(m_page.buttonAutoLevels, SIGNAL(clicked()), SLOT(slot_buttonAutoLevels_clicked()));
118 connect(m_page.buttonAutoLevelsAllChannels, SIGNAL(clicked()), SLOT(slot_buttonAutoLevelsAllChannels_clicked()));
119}
120
123
125{
128
129 KIS_ASSERT_RECOVER(m_activeChannel < m_levelsCurves.size()) { return config; }
130
133 config->setUseLightnessMode(m_page.buttonLightnessMode->isChecked());
134 config->setShowLogarithmicHistogram(m_page.buttonLogarithmicHistogram->isChecked());
135
136 return config;
137}
138
140{
141 const KisLevelsFilterConfiguration *filterConfig =
142 dynamic_cast<const KisLevelsFilterConfiguration *>(config.data());
143 KIS_SAFE_ASSERT_RECOVER_RETURN(filterConfig);
144
145 {
146 KisSignalsBlocker blocker(this, m_page.buttonLightnessMode, m_page.buttonAllChannelsMode);
147
148 if (filterConfig->levelsCurves().empty() || filterConfig->levelsCurves().size() > m_virtualChannels.size()) {
156 KisPropertiesConfigurationSP defaultConfiguration =
158 KisLevelsFilterConfiguration *defaultFilterConfig =
159 dynamic_cast<KisLevelsFilterConfiguration*>(defaultConfiguration.data());
160 KIS_SAFE_ASSERT_RECOVER_RETURN(defaultFilterConfig);
161
162 if (filterConfig->levelsCurves().size() > m_virtualChannels.size()) {
163 QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The current configuration was created for a different colorspace and cannot be used.\nThe channels will be reset."));
164 warnKrita << "WARNING: trying to load levels info with invalid number of channels!";
165 warnKrita << "WARNING: expected:" << m_virtualChannels.size();
166 warnKrita << "WARNING: got:" << filterConfig->levelsCurves().size();
167 }
168
169 defaultFilterConfig->setLightnessLevelsCurve(filterConfig->lightnessLevelsCurve());
170 defaultFilterConfig->setUseLightnessMode(filterConfig->useLightnessMode());
171 defaultFilterConfig->setShowLogarithmicHistogram(filterConfig->showLogarithmicHistogram());
172 setConfiguration(defaultConfiguration);
173
174 return;
175 }
176
177 if (filterConfig->levelsCurves().size() < m_virtualChannels.size()) {
178 // The configuration does not cover all our channels.
179 // This happens when loading a document from an older version, which supported fewer channels.
180 // Reset to make sure the unspecified channels have their default values.
182 }
183
184 for (int ch = 0; ch < filterConfig->levelsCurves().size(); ++ch) {
185 m_levelsCurves[ch] = filterConfig->levelsCurves()[ch];
186 }
187
189
190
191 if (filterConfig->showLogarithmicHistogram()) {
192 m_page.buttonLogarithmicHistogram->setChecked(true);
193 } else {
194 m_page.buttonLinearHistogram->setChecked(true);
195 }
196
197 if (filterConfig->useLightnessMode()) {
198 m_page.buttonLightnessMode->setChecked(true);
199 slot_buttonGroupMode_buttonToggled(m_page.buttonLightnessMode, true);
200 } else {
201 m_page.buttonAllChannelsMode->setChecked(true);
202 slot_buttonGroupMode_buttonToggled(m_page.buttonAllChannelsMode, true);
203 }
204 }
205
207}
208
215
222
229
231{
232 for (KisLevelsCurve &levelsCurve : m_levelsCurves) {
233 levelsCurve.resetAll();
234 }
237}
238
249
251{
253 for (int i = 0; i < m_virtualChannels.size(); ++i) {
254 const VirtualChannelInfo &channel = m_virtualChannels[i];
255 const KoChannelInfo::enumChannelValueType valueType = channel.valueType();
256
257 switch (valueType) {
260 break;
263 break;
264 default:
265 //Hack Alert: should be changed to float
266 int min, max;
268 if (i < m_dev->colorSpace()->channels().length()) {
269 min = m_colorSpace->channels()[i]->getUIMin();
270 max = m_colorSpace->channels()[i]->getUIMax();
271 } else {
272 // it must be "Hue", "Saturation" or other "channel" that isn't actually accessible in the color space
273 min = 0;
274 // specific number apparently doesn't matter,
275 // if there is 255, it will work just fine, too
276 max = 100;
277 }
278 } else {
279 min = 0;
280 max = 100;
281 }
282 m_virtualChannelsMinMaxRanges.append({min, max});
283 break;
284 }
285 }
286 for (int i = 0; i < m_virtualChannels.size(); ++i) {
289 break;
290 }
291 }
292}
293
295{
296 KisSignalsBlocker blocker1(m_page.comboBoxChannel, m_page.sliderInputLevels, m_page.sliderOutputLevels);
297 KisSignalsBlocker blocker2(m_page.spinBoxInputBlackPoint, m_page.spinBoxInputWhitePoint, m_page.spinBoxInputGamma,
298 m_page.spinBoxOutputBlackPoint, m_page.spinBoxOutputWhitePoint);
299
300 if (m_page.buttonLightnessMode->isChecked()) {
301 m_page.comboBoxChannel->hide();
302 m_page.containerAllChannels->hide();
303 } else {
304 m_page.comboBoxChannel->show();
305 m_page.containerAllChannels->show();
306 const int index = m_page.comboBoxChannel->findData(m_activeChannel);
307 m_page.comboBoxChannel->setCurrentIndex(index);
308 }
309
310 m_page.sliderInputLevels->reset(m_activeLevelsCurve->inputBlackPoint(),
313 m_page.sliderOutputLevels->reset(m_activeLevelsCurve->outputBlackPoint(),
315 {
316 QColor leftColor(Qt::black), rightColor(Qt::white);
317 if (m_page.buttonAllChannelsMode->isChecked() &&
321 const int channelIndex = m_virtualChannels[m_activeChannel].pixelIndex();
323 leftColor = Qt::black;
324 rightColor = channelIndex == 0 ? Qt::blue : (channelIndex == 1 ? Qt::green : Qt::red);
325 } else {
326 leftColor = Qt::white;
327 rightColor = channelIndex == 0 ? Qt::cyan : (channelIndex == 1 ? Qt::magenta : (channelIndex == 2 ? Qt::yellow : Qt::black));
328 rightColor = KoColor(rightColor, KoColorSpaceRegistry::instance()->rgb8())
332 .toQColor();
333 }
334 } else if (m_page.buttonAllChannelsMode->isChecked() &&
337 leftColor = Qt::white;
338 rightColor = Qt::black;
339 }
340 m_page.sliderInputLevels->setHandleColor(0, leftColor);
341 m_page.sliderInputLevels->setHandleColor(1, KisPaintingTweaks::blendColors(leftColor, rightColor, 0.5));
342 m_page.sliderInputLevels->setHandleColor(2, rightColor);
343 m_page.sliderOutputLevels->setHandleColor(0, leftColor);
344 m_page.sliderOutputLevels->setHandleColor(1, rightColor);
345 }
346
347 m_page.spinBoxInputBlackPoint->setRange(m_activeChannelMin, m_activeChannelMax);
348 m_page.spinBoxInputWhitePoint->setRange(m_activeChannelMin, m_activeChannelMax);
349 m_page.spinBoxOutputBlackPoint->setRange(m_activeChannelMin, m_activeChannelMax);
350 m_page.spinBoxOutputWhitePoint->setRange(m_activeChannelMin, m_activeChannelMax);
353 m_page.spinBoxInputGamma->setValue(m_activeLevelsCurve->inputGamma());
356
357 if ((m_page.buttonLightnessMode->isChecked() ||
360
361 m_page.buttonAutoLevels->setEnabled(true);
362
365 m_page.buttonAutoLevels->setEnabled(true);
366 } else {
367 m_page.buttonAutoLevels->setEnabled(false);
368 }
369}
370
372{
373 QVector<KisHistogram*> histograms;
375 QVector<QVector<int>> channels;
376
377 // Init lightness histogram
378 // This is used in the simple mode and if the lightness virtual channel is selected
380 KoHistogramProducer *lightnessHistogramProducer = new KoGenericLabHistogramProducer();
381 m_lightnessHistogram.reset(new KisHistogram(m_dev, m_dev->exactBounds(), lightnessHistogramProducer, LINEAR));
382 histograms.append(m_lightnessHistogram.data());
383 colorSpaces.append(KoColorSpaceRegistry::instance()->lab16());
384 channels.append(QVector<int>({0}));
385 }
386 // Init channels histogram
387 if (!m_channelsHistogram) {
388 const KoColorSpace *colorSpace = m_colorSpace;
390 if (keys.size() > 0) {
393 histograms.append(m_channelsHistogram.data());
394 colorSpaces.append(colorSpace);
395 channels.append(QVector<int>());
396 }
397 }
398
399 if (histograms.size() > 0) {
400 m_page.widgetHistogram->setup(histograms, colorSpaces, channels);
402 m_page.widgetHistogram->setDefaultColor(Qt::gray);
403 }
404}
405
407{
408 m_page.widgetHistogram->clearChannels();
409
410 if (m_page.buttonLightnessMode->isChecked() ||
413 m_page.widgetHistogram->setChannel(0, 0);
414 }
415 } else {
418 (channelType == VirtualChannelInfo::ALL_COLORS || channelType == VirtualChannelInfo::REAL)) {
419
420 if (channelType == VirtualChannelInfo::ALL_COLORS) {
421 QVector<int> channels;
422 for (const VirtualChannelInfo &channelInfo : m_virtualChannels) {
423 if (channelInfo.type() == VirtualChannelInfo::REAL && !channelInfo.isAlpha()) {
424 channels.append(channelInfo.pixelIndex());
425 }
426 }
427 m_page.widgetHistogram->setChannels(channels, 1);
428
429 } else if (channelType == VirtualChannelInfo::REAL) {
430 m_page.widgetHistogram->setChannel(m_virtualChannels[m_activeChannel].pixelIndex(), 1);
431 }
432 }
433 }
434}
435
437{
438 m_page.buttonLightnessMode->setIcon(KisIconUtils::loadIcon("color-adjustment-mode-lightness"));
439 m_page.buttonAllChannelsMode->setIcon(KisIconUtils::loadIcon("color-adjustment-mode-channels"));
440 m_page.buttonLinearHistogram->setIcon(KisIconUtils::loadIcon("histogram-linear"));
441 m_page.buttonLogarithmicHistogram->setIcon(KisIconUtils::loadIcon("histogram-logarithmic"));
442 m_page.buttonScaleHistogramToFit->setIcon(KisIconUtils::loadIcon("histogram-show-all"));
443 m_page.buttonScaleHistogramToCutLongPeaks->setIcon(KisIconUtils::loadIcon("histogram-show-best"));
444 m_page.buttonResetAll->setIcon(KisIconUtils::loadIcon("reload-preset"));
445 m_page.buttonResetInputLevels->setIcon(KisIconUtils::loadIcon("reload-preset"));
446 m_page.buttonResetOutputLevels->setIcon(KisIconUtils::loadIcon("reload-preset"));
447 m_page.buttonResetAllChannels->setIcon(KisIconUtils::loadIcon("reload-preset"));
448 m_page.buttonAutoLevels->setIcon(KisIconUtils::loadIcon("autolevels"));
449 m_page.buttonAutoLevelsAllChannels->setIcon(KisIconUtils::loadIcon("autolevels"));
450}
451
470
472{
473 const int virtualChannel = m_page.comboBoxChannel->itemData(index).toInt();
474 setActiveChannel(virtualChannel);
476}
477
479{
480 if (!checked) {
481 return;
482 }
483 m_page.widgetHistogram->setLogarithmic(button == m_page.buttonLogarithmicHistogram);
484}
485
487{
488 if (value >= m_page.spinBoxInputWhitePoint->value()) {
489 m_page.spinBoxInputBlackPoint->setValue(m_page.spinBoxInputWhitePoint->value() - 1);
490 }
491
492 KisSignalsBlocker blocker(m_page.sliderInputLevels);
493 const qreal normalizedValue = normalizeValue(value, m_activeChannelMin, m_activeChannelMax);
494 m_activeLevelsCurve->setInputBlackPoint(normalizedValue);
495 m_page.sliderInputLevels->setBlackPoint(normalizedValue);
497}
498
500{
501 if (value <= m_page.spinBoxInputBlackPoint->value()) {
502 m_page.spinBoxInputWhitePoint->setValue(m_page.spinBoxInputBlackPoint->value() + 1);
503 }
504
505 KisSignalsBlocker blocker(m_page.sliderInputLevels);
506 const qreal normalizedValue = normalizeValue(value, m_activeChannelMin, m_activeChannelMax);
507 m_activeLevelsCurve->setInputWhitePoint(normalizedValue);
508 m_page.sliderInputLevels->setWhitePoint(normalizedValue);
510}
511
513{
514 KisSignalsBlocker blocker(m_page.sliderInputLevels);
516 m_page.sliderInputLevels->setGamma(value);
518}
519
521{
522 KisSignalsBlocker blocker(m_page.sliderOutputLevels);
523 const qreal normalizedValue = normalizeValue(value, m_activeChannelMin, m_activeChannelMax);
525 m_page.sliderOutputLevels->setBlackPoint(normalizedValue);
527}
528
530{
531 KisSignalsBlocker blocker(m_page.sliderOutputLevels);
532 const qreal normalizedValue = normalizeValue(value, m_activeChannelMin, m_activeChannelMax);
534 m_page.sliderOutputLevels->setWhitePoint(normalizedValue);
536}
537
545
553
555{
556 KisSignalsBlocker blocker(m_page.spinBoxInputGamma);
558 m_page.spinBoxInputGamma->setValue(value);
560}
561
569
577
579{
580 KisLevelsCurve previousLevelsCurve = *m_activeLevelsCurve;
581
582 KoDialog *autolevelsDialog = new KoDialog(this);
583
584 m_autoLevelsWidget = new KisAutoLevelsWidget(autolevelsDialog);
585 // Lock contrast method
588 );
590 // Set some default parameters based on the selected channel. These were
591 // selected empirically, there is no strong reason why they should be like this
592 if (m_page.buttonLightnessMode->isChecked() ||
601 if (m_virtualChannels[m_activeChannel].pixelIndex() == 0 ||
602 m_virtualChannels[m_activeChannel].pixelIndex() == 1 ||
603 m_virtualChannels[m_activeChannel].pixelIndex() == 2) {
605 }
606 }
607 // Set the output colors
610 m_autoLevelsWidget->setShadowsColor(KoColor(Qt::black, outputColorsColorSpace));
611 m_autoLevelsWidget->setHighlightsColor(KoColor(Qt::white, outputColorsColorSpace));
612 // Ensure that the midtone color has 50% value
613 QVector<float> normalizedMidtonesColor{0.5, 1.0};
614 KoColor midtonesColor(outputColorsColorSpace);
615 outputColorsColorSpace->fromNormalisedChannelsValue(midtonesColor.data(), normalizedMidtonesColor);
616 m_autoLevelsWidget->setMidtonesColor(midtonesColor);
617
618 connect(m_autoLevelsWidget, SIGNAL(parametersChanged()), SLOT(slot_autoLevelsWidget_parametersChanged()));
620
621 autolevelsDialog->setCaption(i18nc("@title:window", "Auto Levels"));
622 autolevelsDialog->setMainWidget(m_autoLevelsWidget);
623 autolevelsDialog->setAttribute(Qt::WA_DeleteOnClose);
624 connect(autolevelsDialog, &QDialog::rejected,
625 [this, previousLevelsCurve]()
626 {
627 *m_activeLevelsCurve = previousLevelsCurve;
630 }
631 );
632 connect(autolevelsDialog, &QDialog::finished, [this](){ setEnabled(true); });
633
634 setEnabled(false);
635 autolevelsDialog->setEnabled(true);
636
637 autolevelsDialog->show();
638 autolevelsDialog->raise();
639 autolevelsDialog->activateWindow();
640}
641
643{
644 // We can not use the copy constructor here since it makes use of implicit
645 // sharing. We maintain a pointer to the active levels info in m_levelsCurves.
646 // So, if we change an element of m_levelsCurves that would create a new
647 // vector and the pointer will be invalidated
648 QVector<KisLevelsCurve> previousLevelsCurves;
649 for (const KisLevelsCurve &levelsCurve : m_levelsCurves) {
650 previousLevelsCurves.append(levelsCurve);
651 }
652
653 KoDialog *autolevelsDialog = new KoDialog(this);
654
655 m_autoLevelsWidget = new KisAutoLevelsWidget(autolevelsDialog);
658 );
659
663 // Ensure that the midtone color has 50% value
664 QVector<float> normalizedMidtonesColor{0.5, 0.5, 0.5, 1.0};
665 KoColor midtonesColor(m_colorSpace);
666 m_colorSpace->fromNormalisedChannelsValue(midtonesColor.data(), normalizedMidtonesColor);
667 m_autoLevelsWidget->setMidtonesColor(midtonesColor);
668
671
672 autolevelsDialog->setCaption(i18nc("@title:window", "Auto Levels"));
673 autolevelsDialog->setMainWidget(m_autoLevelsWidget);
674 autolevelsDialog->setAttribute(Qt::WA_DeleteOnClose);
675 connect(autolevelsDialog, &QDialog::rejected,
676 [this, previousLevelsCurves]()
677 {
678 // We maintain a pointer to the active levels info in m_levelsCurves
679 // so we use this loop instead of the assignment operator to avoid
680 // invalidation of the pointer
681 for (int i = 0; i < m_levelsCurves.size(); ++i) {
682 m_levelsCurves[i] = previousLevelsCurves[i];
683 }
686 }
687 );
688 connect(autolevelsDialog, &QDialog::finished, [this](){ setEnabled(true); });
689
690 setEnabled(false);
691 autolevelsDialog->setEnabled(true);
692
693 autolevelsDialog->show();
694 autolevelsDialog->raise();
695 autolevelsDialog->activateWindow();
696}
697
699{
700 if (e->type() == QEvent::StyleChange) {
701 m_page.spacer01->changeSize(0, m_page.comboBoxChannel->sizeHint().height(),
702 QSizePolicy::Expanding, QSizePolicy::Fixed);
703 }
704 return QWidget::event(e);
705}
706
708{
709 KisHistogram *histogram;
710 int channel;
711 bool isCMYK = false;
712
713 if (m_page.buttonLightnessMode->isChecked() ||
715 histogram = m_lightnessHistogram.data();
716 channel = 0;
717 } else {
718 histogram = m_channelsHistogram.data();
719 channel = m_virtualChannels[m_activeChannel].pixelIndex();
721 }
722
723 // Output colors
726 const KoColor shadowsColor = m_autoLevelsWidget->outputShadowsColor().convertedTo(outputColorsColorSpace);
727 const KoColor highlightsColor = m_autoLevelsWidget->outputHighlightsColor().convertedTo(outputColorsColorSpace);
728 const KoColor midtonesColor = m_autoLevelsWidget->outputMidtonesColor().convertedTo(outputColorsColorSpace);
729 QVector<float> shadowsNormalizedColor(outputColorsColorSpace->channelCount());
730 QVector<float> highlightsNormalizedColor(outputColorsColorSpace->channelCount());
731 QVector<float> midtonesNormalizedColor(outputColorsColorSpace->channelCount());
732 outputColorsColorSpace->normalisedChannelsValue(shadowsColor.data(), shadowsNormalizedColor);
733 outputColorsColorSpace->normalisedChannelsValue(highlightsColor.data(), highlightsNormalizedColor);
734 outputColorsColorSpace->normalisedChannelsValue(midtonesColor.data(), midtonesNormalizedColor);
735 const qreal shadowsOutput = isCMYK ? 1.0 - highlightsNormalizedColor[0] : shadowsNormalizedColor[0];
736 const qreal highlightsOutput = isCMYK ? 1.0 - shadowsNormalizedColor[0] : highlightsNormalizedColor[0];
737 const qreal midtonesOutput = midtonesNormalizedColor[0];
738
739 QVector<KisAutoLevels::ChannelHistogram> channelsHistograms{{histogram, channel}};
740
743 {histogram, channel},
744 channelsHistograms,
750 {shadowsOutput},
751 {highlightsOutput},
752 {midtonesOutput}
753 )[0];
754
757}
758
760{
761 // histograms
763 for (const VirtualChannelInfo &virtualChannelInfo : m_virtualChannels) {
764 if (virtualChannelInfo.type() == VirtualChannelInfo::REAL && !virtualChannelInfo.isAlpha()) {
765 channelsHistograms.append({m_channelsHistogram.data(), virtualChannelInfo.pixelIndex()});
766 }
767 }
768
769 // Output colors
770 QVector<qreal> shadowsOutput, highlightsOutput, midtonesOutput;
774 QVector<float> shadowsNormalizedColor(m_colorSpace->channelCount());
775 QVector<float> highlightsNormalizedColor(m_colorSpace->channelCount());
776 QVector<float> midtonesNormalizedColor(m_colorSpace->channelCount());
777 m_colorSpace->normalisedChannelsValue(shadowsColor.data(), shadowsNormalizedColor);
778 m_colorSpace->normalisedChannelsValue(highlightsColor.data(), highlightsNormalizedColor);
779 m_colorSpace->normalisedChannelsValue(midtonesColor.data(), midtonesNormalizedColor);
780 for (const KisAutoLevels::ChannelHistogram &channelHistogram : channelsHistograms) {
781 shadowsOutput.append(shadowsNormalizedColor[channelHistogram.channel]);
782 highlightsOutput.append(highlightsNormalizedColor[channelHistogram.channel]);
783 midtonesOutput.append(midtonesNormalizedColor[channelHistogram.channel]);
784 }
785
786 // get levels parameters
787 QVector<KisLevelsCurve> levelsCurves;
789 levelsCurves =
791 channelsHistograms,
797 shadowsOutput,
798 highlightsOutput,
799 midtonesOutput
800 );
801 // get min black point and max white point
802 qreal minBlackPoint = 1.0;
803 qreal maxWhitePoint = 0.0;
804 for (const KisLevelsCurve &levelsCurve : levelsCurves) {
805 minBlackPoint = qMin(levelsCurve.inputBlackPoint(), minBlackPoint);
806 maxWhitePoint = qMax(levelsCurve.inputWhitePoint(), maxWhitePoint);
807 }
808 for (KisLevelsCurve &levelsCurve : levelsCurves) {
809 levelsCurve.setInputBlackPoint(minBlackPoint);
810 levelsCurve.setInputWhitePoint(maxWhitePoint);
811 }
812 } else {
813 levelsCurves =
815 channelsHistograms,
821 shadowsOutput,
822 highlightsOutput,
823 midtonesOutput
824 );
825 }
826
827 // Set parameters
828 for (int i = 0, j = 0; i < m_virtualChannels.size(); ++i) {
829 if (m_virtualChannels[i].type() == VirtualChannelInfo::REAL && !m_virtualChannels[i].isAlpha()) {
830 m_levelsCurves[i] = levelsCurves[j];
831 ++j;
832 }
833 }
836}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
static int deNormalizeValue(qreal value, int min, int max)
static qreal normalizeValue(int value, int min, int max)
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID LABAColorModelID("LABA", ki18n("L*a*b*/Alpha"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
A widget that allows to select a combination of auto levels parameters.
KisAutoLevels::MidtonesAdjustmentMethod midtonesAdjustmentMethod() const
Get the method used to adjust the midtones.
void setMidtonesAdjustmentAmount(qreal newMidtonesAdjustmentAmount)
Set how much the reference midtone point is deviated from the center of the histogram to the median/m...
void setMidtonesColor(const KoColor &newMidtonesColor)
Set the color used to move the midtones towards.
KoColor outputMidtonesColor() const
Get the color used to move the midtones towards.
KisAutoLevels::ShadowsAndHighlightsAdjustmentMethod shadowsAndHighlightsAdjustmentMethod() const
Get the method used to adjust the contrast.
void setShadowsColor(const KoColor &newShadowsColor)
Set the color used to move the shadows towards.
void setMaximumInputBlackAndWhiteOffset(qreal newMaximumInputBlackAndWhiteOffset)
Set the normalized maximum value that the black/white point can be moved from the default position of...
qreal midtonesAdjustmentAmount() const
Get how much the reference midtone point is deviated from the center of the histogram to the median/m...
KoColor outputShadowsColor() const
Get the color used to move the shadows towards.
KoColor outputHighlightsColor() const
Get the color used to move the highlights towards.
qreal highlightsClipping() const
Get the normalized percentage used to clip the highlights.
qreal shadowsClipping() const
Get the normalized percentage used to clip the shadows.
void setShadowsAndHighlightsAdjustmentMethod(KisAutoLevels::ShadowsAndHighlightsAdjustmentMethod newMethod)
Set the method used to adjust the contrast.
void setMidtonesAdjustmentMethod(KisAutoLevels::MidtonesAdjustmentMethod newMethod)
Set the method used to adjust the midtones.
void setHighlightsColor(const KoColor &newHighlightsColor)
Set the color used to move the highlights towards.
void lockShadowsAndHighlightsAdjustmentMethod()
Disables the contrast method combo box. Use this when only one of the methods makes sense.
qreal maximumInputBlackAndWhiteOffset() const
Get the normalized maximum value that the black/white point can be moved from the default position of...
void sigConfigurationItemChanged()
static KisResourcesInterfaceSP instance()
KisLevelsCurve m_lightnessLevelsCurve
void slot_sliderInputLevels_blackPointChanged(qreal value)
void slot_spinBoxOutputWhitePoint_valueChanged(int value)
const KoColorSpace * m_colorSpace
void slot_buttonGroupHistogramMode_buttonToggled(QAbstractButton *button, bool checked)
void slot_sliderOutputLevels_blackPointChanged(qreal value)
void slot_autoLevelsWidgetAllChannels_parametersChanged()
void slot_sliderInputLevels_gammaChanged(qreal value)
KisLevelsCurve * m_activeLevelsCurve
void slot_sliderOutputLevels_whitePointChanged(qreal value)
void slot_buttonGroupMode_buttonToggled(QAbstractButton *button, bool checked)
KisPropertiesConfigurationSP configuration() const override
void slot_spinBoxInputWhitePoint_valueChanged(int value)
void setConfiguration(const KisPropertiesConfigurationSP config) override
KisLevelsConfigWidget(QWidget *parent, KisPaintDeviceSP dev, const KoColorSpace *colorSpace)
KisAutoLevelsWidget * m_autoLevelsWidget
QScopedPointer< KisHistogram > m_lightnessHistogram
QVector< VirtualChannelInfo > m_virtualChannels
void slot_sliderInputLevels_whitePointChanged(qreal value)
void slot_comboBoxChannel_activated(int index)
void slot_spinBoxInputBlackPoint_valueChanged(int value)
Ui::LevelsConfigWidget m_page
void slot_spinBoxOutputBlackPoint_valueChanged(int value)
QPair< int, int > m_lightnessMinMaxRanges
QVector< QPair< int, int > > m_virtualChannelsMinMaxRanges
bool event(QEvent *e) override
QVector< KisLevelsCurve > m_levelsCurves
QScopedPointer< KisHistogram > m_channelsHistogram
void slot_spinBoxInputGamma_valueChanged(qreal value)
This class holds the parameters for a levels adjustment. It is modeled after KisCubicCurve and has si...
qreal outputBlackPoint() const
Get the output black point.
void setOutputBlackPoint(qreal newOutputBlackPoint)
Set the output black point.
qreal inputBlackPoint() const
Get the input black point.
void setOutputWhitePoint(qreal newOutputWhitePoint)
Set the output white point.
qreal inputGamma() const
Get the gamma value.
void setName(const QString &newName)
Set the name associated with this levels info object. This allows us to carry around a display name f...
void resetOutputLevels()
Resets the output levels only.
qreal inputWhitePoint() const
Get the input white point.
void setInputGamma(qreal newInputGamma)
Set the gamma value.
void setInputWhitePoint(qreal newInputWhitePoint)
Set the input white point.
void resetInputLevels()
Resets the input levels only (and gamma)
qreal outputWhitePoint() const
Get the output white point.
void setInputBlackPoint(qreal newInputBlackPoint)
Set the input black point.
void resetAll()
Resets the input and output levels (and gamma)
void setLevelsCurves(const QVector< KisLevelsCurve > &newLevelsCurves)
const KisLevelsCurve lightnessLevelsCurve() const
void setShowLogarithmicHistogram(bool newShowLogarithmicHistogram)
const QVector< KisLevelsCurve > levelsCurves() const
void setUseLightnessMode(bool newUseLightnessMode)
void setLightnessLevelsCurve(const KisLevelsCurve &newLightnessLevelsCurve)
QRect exactBounds() const
enumChannelValueType
enum to define the value of the channel
@ UINT8
use this for an unsigned integer 8bits channel
@ UINT16
use this for an integer 16bits channel
QList< KoChannelInfo * > channels
virtual KoID colorModelId() const =0
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
KoColor convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
Definition KoColor.cpp:163
quint8 * data()
Definition KoColor.h:144
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
A dialog base class with standard buttons and predefined layouts.
Definition KoDialog.h:116
void setMainWidget(QWidget *widget)
Definition KoDialog.cpp:354
virtual void setCaption(const QString &caption)
Definition KoDialog.cpp:498
T get(const QString &id) const
QList< QString > keysCompatibleWith(const KoColorSpace *colorSpace, bool isStrict=false) const
returns a list, sorted by preference: higher preference comes first
static KoHistogramProducerFactoryRegistry * instance()
virtual KoHistogramProducer * generate()=0
Factory method, generates a new KoHistogramProducer.
QString id() const
Definition KoID.cpp:63
KoChannelInfo::enumChannelValueType valueType() const
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define warnKrita
Definition kis_debug.h:87
QString button(const QWheelEvent &ev)
QVector< KisLevelsCurve > adjustMonochromaticContrast(ChannelHistogram lightnessHistogram, QVector< ChannelHistogram > &channelsHistograms, qreal shadowsClipping, qreal highlightsClipping, qreal maximumInputBlackAndWhiteOffset, MidtonesAdjustmentMethod midtonesAdjustmentMethod, qreal midtonesAdjustmentAmount, const QVector< qreal > &outputBlackPoints, const QVector< qreal > &outputWhitePoints, const QVector< qreal > &outputMidtones)
Creates a KisLevelsCurve for every channel in "channelsHistograms". Computes the input black and whit...
QVector< KisLevelsCurve > adjustPerChannelContrast(QVector< ChannelHistogram > &channelsHistograms, qreal shadowsClipping, qreal highlightsClipping, qreal maximumInputBlackAndWhiteOffset, MidtonesAdjustmentMethod midtonesAdjustmentMethod, qreal midtonesAdjustmentAmount, const QVector< qreal > &outputBlackPoints, const QVector< qreal > &outputWhitePoints, const QVector< qreal > &outputMidtones)
Creates a KisLevelsCurve for every channel in "channelsHistograms". Computes the input black and whit...
@ MidtonesAdjustmentMethod_UseMedian
@ ShadowsAndHighlightsAdjustmentMethod_MonochromaticContrast
QIcon loadIcon(const QString &name)
QVector< VirtualChannelInfo > getVirtualChannels(const KoColorSpace *cs, int maxChannels, bool supportsLightness, bool supportsHue, bool supportsSaturation)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
@ LINEAR
Definition nugrid.h:26
Convenience class that associates a KisHistogram and a channel index. This is useful because setting ...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()