Krita Source Code Documentation
Loading...
Searching...
No Matches
KisVisualColorSelector.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Mathias Wein <lynx.mw+kde@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
8
9#include <QVector4D>
10#include <QList>
11#include <QPointer>
12
13#include <KSharedConfig>
14#include <KConfigGroup>
15
18//#include <QPointer>
20#include "kis_debug.h"
21
27
49
52 , m_d(new Private)
53{
55 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
56
58 if (model) {
59 setSelectorModel(model);
60 } else {
62 m_d->selectorModel->slotLoadACSConfig();
63 }
64
65 m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE);
66 connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotReloadConfiguration()), Qt::UniqueConnection);
67}
68
70{
71 delete m_d->updateTimer;
72}
73
75{
76 return QSize(75, 75);
77}
78
80{
81 if (model == m_d->selectorModel) {
82 return;
83 }
84 if (m_d->selectorModel) {
85 m_d->selectorModel->disconnect(this);
86 }
87 connect(model.data(), SIGNAL(sigChannelValuesChanged(QVector4D,quint32)),
88 SLOT(slotChannelValuesChanged(QVector4D,quint32)));
89 connect(model.data(), SIGNAL(sigColorModelChanged()), SLOT(slotColorModelChanged()));
90 connect(model.data(), SIGNAL(sigColorSpaceChanged()), SLOT(slotColorSpaceChanged()));
91 // to keep the KisColorSelectorInterface API functional:
92 connect(model.data(), SIGNAL(sigNewColor(KoColor)), this, SIGNAL(sigNewColor(KoColor)));
93 m_d->selectorModel = model;
94 m_d->initialized = false;
96}
97
99{
100 return m_d->selectorModel;
101}
102
103void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate)
104{
105 Q_UNUSED(forceSelfUpdate);
106 if (forceCircular != m_d->circular) {
107 m_d->circular = forceCircular;
108 m_d->initialized = false;
110 }
111}
112
114{
115 return m_d->acs_config;
116}
117
119{
120 m_d->useACSConfig = !config;
121 if (config) {
122 // applies immediately, while signalled rebuilds from krita configuration changes
123 // are queued, so make sure we cancel queued updates
124 m_d->updateTimer->stop();
126 if (configNew != m_d->acs_config) {
127 m_d->acs_config = configNew;
128 m_d->initialized = false;
130 }
131 } else {
132 m_d->initialized = false;
133 m_d->updateTimer->start();
134 }
135}
136
138{
139 m_d->acceptTabletEvents = on;
140 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
141 shape->setAcceptTabletEvents(on);
142 }
143}
144
146{
147 if (m_d->selectorModel) {
148 return m_d->selectorModel->currentColor();
149 }
150 return KoColor();
151}
152
154{
155 int newWidth = qMax(5, width);
156 if (newWidth != m_d->minimumSliderWidth) {
157 m_d->minimumSliderWidth = width;
159 }
160}
161
163{
164 return m_d->displayRenderer ? m_d->displayRenderer : KoDumbColorDisplayRenderer::instance();
165}
166
171
173{
174 if (mode != m_d->renderMode) {
175 m_d->renderMode = mode;
176 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
177 shape->forceImageUpdate();
178 shape->update();
179 }
180 }
181}
182
184{
185 return m_d->autoAdjustExposure;
186}
187
189{
190 m_d->autoAdjustExposure = enabled;
191}
192
194{
195 return m_d->proofColors;
196}
197
199{
200 if (enabled != m_d->proofColors) {
201 m_d->proofColors = enabled;
202 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
203 shape->forceImageUpdate();
204 shape->update();
205 }
206 }
207}
208
210{
211 if (edge != Qt::TopEdge && edge != Qt::LeftEdge) {
212 return;
213 }
214
215 if (edge != m_d->sliderPosition) {
216 m_d->sliderPosition = edge;
218 }
219}
220
222{
223 return m_d->gamutMask.data();
224}
225
227{
228 if (m_d->selectorModel) {
229 m_d->selectorModel->slotSetColor(c);
230 }
231}
232
234{
235 if (m_d->selectorModel) {
236 m_d->selectorModel->slotSetColorSpace(cs);
237 }
238}
239
241{
242 if (m_d->updateTimer && m_d->useACSConfig) {
243 // NOTE: this timer is because notifyConfigChanged() is only called
244 // via KisConfig::setCustomColorSelectorColorSpace(), but at this point
245 // Advanced Color Selector has not written the relevant config values yet.
246 m_d->initialized = false;
247 m_d->updateTimer->start();
248 }
249}
250
259
261{
262 // Note: KisCanvasResourceProvider currently does not distinguish
263 // between activating, switching and property changes of a gamut mask
264 m_d->gamutMask = mask;
265 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
266 shape->updateGamutMask();
267 }
268}
269
271{
272 m_d->gamutMask.clear();
273 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
274 shape->updateGamutMask();
275 }
276}
277
279{
280 // Shapes currently always requests preview shapes if available, so more of the same...
281 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
282 shape->updateGamutMask();
283 }
284}
285
286void KisVisualColorSelector::slotChannelValuesChanged(const QVector4D &values, quint32 channelFlags)
287{
288 // about to (re-)build selector, values will be fetched when done
289 if (!m_d->initialized) {
290 return;
291 }
292 m_d->channelValues = values;
293 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
294 shape->setChannelValues(m_d->channelValues, channelFlags);
295 }
296}
297
299{
300 // TODO: triangle <=> diamond switch only happens on HSV <=> non-HSV, but
301 // the previous color model is not accessible right now
302 if (!m_d->initialized || m_d->selectorModel->colorChannelCount() != m_d->colorChannelCount
303 || m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
304 m_d->initialized = false;
306 } else {
308 }
309}
310
312{
313 if (m_d->autoAdjustExposure && m_d->selectorModel && m_d->selectorModel->supportsExposure()) {
314 m_d->selectorModel->setMaxChannelValues(calculateMaxChannelValues());
315 }
316}
317
319{
320 const KisVisualColorSelectorShape *shape = qobject_cast<KisVisualColorSelectorShape *>(sender());
322
323 m_d->channelValues[shape->channel(0)] = pos.x();
325 m_d->channelValues[shape->channel(1)] = pos.y();
326 }
327
328 for (KisVisualColorSelectorShape *widget : std::as_const(m_d->widgetlist)) {
329 if (widget != shape){
330 widget->setChannelValues(m_d->channelValues, shape->channelMask());
331 }
332 }
333 m_d->selectorModel->slotSetChannelValues(m_d->channelValues);
334}
335
337{
338 if (m_d->autoAdjustExposure && m_d->selectorModel && m_d->selectorModel->supportsExposure()) {
339 m_d->selectorModel->setMaxChannelValues(calculateMaxChannelValues());
340 }
341 // TODO: can we be smarter about forced updates?
342 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
343 shape->forceImageUpdate();
344 shape->update();
345 }
346}
347
349{
350 if (m_d->useACSConfig) {
352 // this may trigger slotColorModelChanged() so check afterwards if we already rebuild
353 m_d->selectorModel->slotLoadACSConfig();
354 if (!m_d->initialized) {
356 }
357 }
358}
359
361{
362 qDeleteAll(m_d->widgetlist);
363 m_d->widgetlist.clear();
364
365 if (!m_d->selectorModel || m_d->selectorModel->colorModel() == KisVisualColorModel::None) {
366 return;
367 }
368
369 m_d->colorChannelCount = m_d->selectorModel->colorChannelCount();
370
371 bool supportsGamutMask = false;
372
373 //recreate all the widgets.
374
375 if (m_d->colorChannelCount == 1) {
376
378
379 if (m_d->circular) {
382 }
383 else {
385 0, 0, 20);
386 }
387
388 connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
389 m_d->widgetlist.append(bar);
390 }
391 else if (m_d->colorChannelCount == 3) {
392 int channel1 = 0;
393 int channel2 = 1;
394 int channel3 = 2;
395
396 switch(m_d->acs_config.subTypeParameter)
397 {
400 channel1 = 0;
401 break;
403 channel1 = 1;
404 break;
406 channel1 = 2;
407 break;
408 default:
409 Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter");
410 }
411
412 switch(m_d->acs_config.mainTypeParameter)
413 {
415 channel2 = 0;
416 channel3 = 1;
417 break;
419 channel2 = 0;
420 channel3 = 2;
421 break;
423 channel2 = 1;
424 channel3 = 2;
425 break;
426 default:
427 Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter");
428 }
429
431 if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
434 channel1, channel1, 20,
436 }
437 else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) {
440
441 bar = new KisVisualRectangleSelectorShape(this,
443 channel1, channel1, 20, orientation);
444 }
445 else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) {
448 channel1, channel1,
450 } else {
451 // Accessing bar below would crash since it's not initialized.
452 // Hopefully this can never happen.
453 warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape";
454 Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape");
455 return;
456 }
457
458 m_d->widgetlist.append(bar);
459
461 if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
462 if (m_d->selectorModel->colorModel() == KisVisualColorModel::HSV) {
464 channel2, channel3);
465 } else {
467 channel2, channel3);
468 }
469 }
470 else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
472 channel2, channel3);
473 }
474 else {
476 channel2, channel3);
477 }
478
479 supportsGamutMask = block->supportsGamutMask();
480
481 connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
482 connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
483 m_d->widgetlist.append(block);
484 }
485 else if (m_d->colorChannelCount == 4) {
488 connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
489 connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
490 m_d->widgetlist.append(block);
491 m_d->widgetlist.append(block2);
492 }
493
494 m_d->initialized = true;
495 // make sure we call "our" resize function
497
498 for (KisVisualColorSelectorShape *shape : std::as_const(m_d->widgetlist)) {
499 shape->setAcceptTabletEvents(m_d->acceptTabletEvents);
500 // if this widget is currently visible, new children are hidden by default
501 shape->show();
502 }
503
504 // finally update widgets with new channel values
505 slotChannelValuesChanged(m_d->selectorModel->channelValues(), (1u << m_d->colorChannelCount) - 1);
506 emit sigGamutMaskSupportChanged(supportsGamutMask);
507}
508
510{
511 if (!m_d->selectorModel || !m_d->initialized) {
512 KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->widgetlist.isEmpty() || !m_d->initialized);
513 return;
514 }
515 int sizeValue = qMin(width(), height());
516 // due to masking/antialiasing, the visible width is ~4 pixels less, so add them here
517 const int margin = 4;
518 const qreal sliderRatio = 0.09;
519 int borderWidth = qMax(int(sizeValue * sliderRatio), m_d->minimumSliderWidth) + margin;
520 QRect newrect(0,0, this->geometry().width(), this->geometry().height());
521
522 if (m_d->colorChannelCount == 1) {
523 if (m_d->circular) {
524 m_d->widgetlist.at(0)->resize(sizeValue, sizeValue);
525 }
526 else {
527 KisVisualRectangleSelectorShape *slider = qobject_cast<KisVisualRectangleSelectorShape *>(m_d->widgetlist.at(0));
529 if (useHorizontalSlider()) {
530 int sliderWidth = qMax(width()/10, m_d->minimumSliderWidth);
531 sliderWidth = qMin(sliderWidth, height());
532 int y = (height() - sliderWidth)/2;
534 slider->setGeometry(0, y, width(), sliderWidth);
535 }
536 else {
537 // vertical slider
538 int sliderWidth = qMax(height()/10, m_d->minimumSliderWidth);
539 sliderWidth = qMin(sliderWidth, width());
540 int x = (width() - sliderWidth)/2;
542 slider->setGeometry(x, 0, sliderWidth, height());
543 }
544 }
545 }
546 else if (m_d->colorChannelCount == 3) {
547 m_d->widgetlist.at(0)->setBorderWidth(borderWidth);
548 // Ring
549 if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring ||
550 (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular)) {
551
552 m_d->widgetlist.at(0)->setGeometry((width() - sizeValue)/2, (height() - sizeValue)/2,
553 sizeValue, sizeValue);
554 }
555 // Slider Bar
556 else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider) {
557 // limit stretch; only vertical slider currently
558 if (useHorizontalSlider()) {
559 newrect.setWidth(qMin(newrect.width(), qRound((newrect.height() - borderWidth) * m_d->stretchLimit)));
560 newrect.setHeight(qMin(newrect.height(), qRound(sizeValue * m_d->stretchLimit + borderWidth)));
561
562 m_d->widgetlist.at(0)->setGeometry(0, 0, newrect.width(), borderWidth);
563 }
564 else {
565 newrect.setWidth(qMin(newrect.width(), qRound(sizeValue * m_d->stretchLimit + borderWidth)));
566 newrect.setHeight(qMin(newrect.height(), qRound((newrect.width() - borderWidth) * m_d->stretchLimit)));
567
568 m_d->widgetlist.at(0)->setGeometry(0, 0, borderWidth, newrect.height());
569 }
570
571 }
572
573 if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
574 if (m_d->selectorModel->colorModel() == KisVisualColorModel::HSV) {
575 m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect));
576 } else {
577 m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect));
578 }
579 }
580 else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
581 m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect));
582 }
583 else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) {
584 m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect));
585 }
586 // center horizontally
587 QRect boundRect(m_d->widgetlist.at(0)->geometry() | m_d->widgetlist.at(1)->geometry());
588 int offset = (width() - boundRect.width()) / 2 - boundRect.left();
589 m_d->widgetlist.at(0)->move(m_d->widgetlist.at(0)->pos() + QPoint(offset, 0));
590 m_d->widgetlist.at(1)->move(m_d->widgetlist.at(1)->pos() + QPoint(offset, 0));
591 }
592 else if (m_d->colorChannelCount == 4) {
593 int sizeBlock = qMin(width()/2 - 8, height());
594 m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock);
595 m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock);
596 }
597}
598
600{
601 if (m_d->colorChannelCount == 1) {
602 return width() > height();
603 }
604 else {
605 return m_d->sliderPosition == Qt::TopEdge;
606 }
607}
608
610{
611 if (displayRenderer != m_d->displayRenderer) {
612 if (m_d->displayRenderer) {
613 m_d->displayRenderer->disconnect(this);
614 }
615 if (displayRenderer) {
616 connect(displayRenderer, SIGNAL(displayConfigurationChanged()),
617 SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection);
618 }
619 m_d->displayRenderer = displayRenderer;
620 }
621}
622
624{
625 // Note: This calculation only makes sense for HDR color spaces
626 QVector4D maxChannelValues = QVector4D(1, 1, 1, 1);
627 const QList<KoChannelInfo *> channels = m_d->selectorModel->colorSpace()->channels();
628
629 for (int i = 0; i < channels.size(); i++) {
630 const KoChannelInfo *channel = channels.at(i);
631 if (channel->channelType() != KoChannelInfo::ALPHA) {
632 quint32 logical = channel->displayPosition();
633 if (logical > m_d->selectorModel->colorSpace()->alphaPos()) {
634 --logical;
635 }
636 maxChannelValues[logical] = displayRenderer()->maxVisibleFloatValue(channel);
637 }
638 }
639
640 return maxChannelValues;
641}
642
644{
645 KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
647 cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
648 m_d->acs_config = validatedConfiguration(raw_config);
649}
650
652{
653 KisColorSelectorConfiguration validated(cfg);
654 bool ok = true;
655
656 switch (validated.mainType) {
660 break;
661 default:
662 ok = false;
663 }
664
665 switch (validated.subType) {
668 break;
669 default:
670 ok = false;
671 }
672
673 switch(validated.subTypeParameter)
674 {
678 break;
679 // translate to HSV
684 break;
689 break;
690 default:
691 ok = false;
692 }
693
694 switch(validated.mainTypeParameter)
695 {
699 break;
700 // translate to HSV
706 break;
711 break;
716 break;
717 default:
718 ok = false;
719 }
720
721 if (ok) {
722 return validated;
723 }
728}
QSharedPointer< KisVisualColorModel > KisVisualColorModelSP
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static KisColorSelectorConfiguration fromString(QString string)
void sigNewColor(const KoColor &c)
The KisVisualColorModel class allows manipulating a KoColor using various color models.
The KisVisualColorSelectorShape class A 2d widget can represent at maximum 2 coordinates....
int channel(int dimension) const
channel Get the channel index associated with a selector shape dimension
Dimensions getDimensions() const
getDimensions
const QScopedPointer< Private > m_d
bool autoAdjustExposure() const
Get the state of automatic exposure adjustment. If enabled, the selector will set new maximum channel...
void slotSetColorSpace(const KoColorSpace *cs) override
void switchDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
void setSelectorModel(KisVisualColorModelSP model)
void setRenderMode(RenderMode mode)
const KoColorDisplayRendererInterface * displayRenderer() const
void setAutoAdjustExposure(bool enabled)
void slotChannelValuesChanged(const QVector4D &values, quint32 channelFlags)
KisVisualColorSelector(QWidget *parent=0, KisVisualColorModelSP model=KisVisualColorModelSP())
KisVisualColorSelector constructor.
const KisColorSelectorConfiguration & configuration() const
void setConfiguration(const KisColorSelectorConfiguration *config)
Explicitly set the shape configuration. Accepts all valid combinations of Advanced Color Selector,...
KoGamutMask * activeGamutMask() const
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) override
void resizeEvent(QResizeEvent *) override
void setSliderPosition(Qt::Edge edge)
Set the slider position for slider + square and slider + wheel configurations.
void setConfig(bool forceCircular, bool forceSelfUpdate) override
setConfig
void slotSetColor(const KoColor &c) override
KoColor getCurrentColor() const override
void sigGamutMaskSupportChanged(bool supported)
sigGamutMaskSupportChanged Signals whether gamut masks are supported by the current selector shape.
QSize minimumSizeHint() const override
static KisColorSelectorConfiguration validatedConfiguration(const KisColorSelectorConfiguration &cfg)
KisVisualColorModelSP selectorModel() const
void slotGamutMaskChanged(KoGamutMaskSP mask)
void setBorderWidth(int width) override
setBorderWidth set the border of the single dimensional selector.
@ ALPHA
The channel represents the opacity of a pixel.
enumChannelType channelType() const
qint32 displayPosition() const
virtual qreal maxVisibleFloatValue(const KoChannelInfo *chaninfo) const =0
static KoColorDisplayRendererInterface * instance()
The resource type for gamut masks used by the artistic color selector.
Definition KoGamutMask.h:44
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define warnUI
Definition kis_debug.h:94
QList< KisVisualColorSelectorShape * > widgetlist
KisVisualColorSelector::RenderMode renderMode
QPointer< const KoColorDisplayRendererInterface > displayRenderer
KisColorSelectorConfiguration acs_config