Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_small_color_widget.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
8#include <QVBoxLayout>
10#include <ImfRgba.h>
11
12#include <KoColorConversions.h>
13#include <KoColorProfile.h>
14
15#include "kis_debug.h"
16#include "kis_assert.h"
17
18#include <KoColor.h>
19#include "KisGLImageF16.h"
20#include "KisGLImageWidget.h"
25
30#include <KisDisplayConfig.h>
31
33
34#include <config-use-surface-color-management-api.h>
35#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
36
38
39#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
40
41
43 qreal hue; // 0 ... 1.0
44 qreal value; // 0 ... 1.0
45 qreal saturation; // 0 ... 1.0
53 QScopedPointer<KisSignalCompressorWithParam<int>> dynamicRangeCompressor;
60 bool hasHDR = false;
61 bool hasHardwareHDR = false;
62
63#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
64 KisRootSurfaceInfoProxy *rootSurfaceInfoProxy = nullptr;
65#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
66
69 }
70
73
74 if (KisOpenGLModeProber::instance()->useHDRMode()) {
76#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
77 } else if (KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS()) {
78 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(rootSurfaceInfoProxy, profile);
79 profile = rootSurfaceInfoProxy->rootSurfaceProfile();
80#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
81 } else {
82 // we are a normal QWidget's surface
84 }
85
86 return profile;
87 }
88
96
99
100 if (!result || result->colorModelId() != RGBAColorModelID) {
101 result = outputColorSpace();
102 } else if (result && result->colorDepthId() != Float32BitsColorDepthID) {
104 colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), result->profile());
105 }
106
107 // PQ color space we delinearize into linear one
108 if (result
109 && result->colorModelId() == RGBAColorModelID
110 && result->profile()
112
116 }
117
118 return result;
119 }
120};
121
123 : QWidget(parent),
124 d(new Private)
125{
126 d->hue = 0.0;
127 d->value = 0;
128 d->saturation = 0;
129 d->updateAllowed = true;
130
132 connect(d->repaintCompressor, SIGNAL(timeout()), SLOT(update()));
133
135 connect(d->resizeUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdatePalettes()));
136
138 connect(d->valueSliderUpdateCompressor, SIGNAL(timeout()), SLOT(updateSVPalette()));
139
141 connect(d->colorChangedSignalCompressor, SIGNAL(timeout()), SLOT(slotTellColorChanged()));
142
143 {
144 using namespace std::placeholders;
145 std::function<void (qreal)> callback(
146 std::bind(&KisSmallColorWidget::updateDynamicRange, this, _1));
148
149 }
150
151 // default color space means that the colors will be passed through to the GPU directly
152 // without any conversion
154
155 d->hueWidget = new KisClickableGLImageWidget(colorSpace, this);
156 d->hueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
158 connect(d->hueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotHueSliderChanged(const QPointF&)));
159
160 d->valueWidget = new KisClickableGLImageWidget(colorSpace, this);
161 d->valueWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
163 connect(d->valueWidget, SIGNAL(selected(const QPointF&)), SLOT(slotValueSliderChanged(const QPointF&)));
164
166
167#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
168 if (KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS()) {
172 d->hasHardwareHDR = true;
173 d->rootSurfaceInfoProxy = new KisRootSurfaceInfoProxy(this, this);
174 connect(d->rootSurfaceInfoProxy, &KisRootSurfaceInfoProxy::sigRootSurfaceProfileChanged,
176 }
177#endif
178
179 if (d->hasHardwareHDR) {
180 d->dynamicRange = new KisSliderSpinBox(this);
181 d->dynamicRange->setRange(80, 10000);
183 d->dynamicRange->setSingleStep(1);
185 d->dynamicRange->setSuffix("cd/m²");
187 connect(d->dynamicRange, SIGNAL(valueChanged(int)), SLOT(slotInitiateUpdateDynamicRange(int)));
188 }
189
190 QVBoxLayout *layout = new QVBoxLayout(this);
191 layout->addWidget(d->hueWidget, 0);
192 layout->addWidget(d->valueWidget, 1);
193
194 if (d->dynamicRange) {
195 layout->addSpacing(16);
196 layout->addWidget(d->dynamicRange, 0);
197 }
198
200}
201
206
208{
209 h = qBound(0.0, h, 1.0);
210 d->hue = h;
214}
215
216void KisSmallColorWidget::setHSV(qreal h, qreal s, qreal v, bool notifyChanged)
217{
218 h = qBound(0.0, h, 1.0);
219 s = qBound(0.0, s, 1.0);
220 v = qBound(0.0, v, 1.0);
221 bool newH = !qFuzzyCompare(d->hue, h);
222 d->hue = h;
223 d->value = v;
224 d->saturation = s;
225 // TODO: remove and make acyclic!
226 if (notifyChanged) {
228 }
229 if(newH) {
231 }
233}
234
236{
237 if (!d->updateAllowed) return;
238
241 }
242
244
245 const KoColorSpace *cs = d->generationColorSpace();
247
248 KoColor newColor(color);
249 newColor.convertTo(cs);
250
251 QVector<float> channels(4);
252 cs->normalisedChannelsValue(newColor.data(), channels);
253
254 float r, g, b;
255
257 r = channels[2];
258 g = channels[1];
259 b = channels[0];
260 } else {
261 r = channels[0];
262 g = channels[1];
263 b = channels[2];
264 }
265
266 if (d->hasHDR) {
267 qreal rangeCoeff = d->effectiveRelativeDynamicRange();
268
269 if (rangeCoeff < r || rangeCoeff < g || rangeCoeff < b) {
270 rangeCoeff = std::max({r, g, b}) * 1.10f;
271
272 const int newMaxLuminance = qRound(d->currenOutputReferenceWhite * rangeCoeff);
273 updateDynamicRange(newMaxLuminance);
274 d->dynamicRange->setValue(newMaxLuminance);
275 }
276
277 r /= rangeCoeff;
278 g /= rangeCoeff;
279 b /= rangeCoeff;
280 } else {
281 r = qBound(0.0f, r, 1.0f);
282 g = qBound(0.0f, g, 1.0f);
283 b = qBound(0.0f, b, 1.0f);
284 }
285
286 float denormHue, saturation, value;
287 RGBToHSV(r, g, b, &denormHue, &saturation, &value);
288
289 d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0.0));
290 d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value));
291
292 setHSV(denormHue / 360.0, saturation, value, false);
293}
294
300
301namespace {
302struct FillHPolicy {
303 static inline void getRGB(qreal /*hue*/, float xPortionCoeff, float /*yPortionCoeff*/,
304 int x, int /*y*/, float *r, float *g, float *b) {
305
306 HSVToRGB(xPortionCoeff * x * 360.0f, 1.0, 1.0, r, g, b);
307 }
308};
309
310struct FillSVPolicy {
311 static inline void getRGB(qreal hue, float xPortionCoeff, float yPortionCoeff,
312 int x, int y, float *r, float *g, float *b) {
313
314 HSVToRGB(hue * 360.0, xPortionCoeff * x, 1.0 - yPortionCoeff * y, r, g, b);
315 }
316};
317}
318
319template<class FillPolicy>
321{
322 if (!isEnabled() || size.isEmpty()) return;
323
324 KisGLImageF16 image(size);
325 const float xPortionCoeff = 1.0 / image.width();
326 const float yPortionCoeff = 1.0 / image.height();
327 const float rangeCoeff = d->effectiveRelativeDynamicRange();
328
329 const KoColorSpace *generationColorSpace = d->generationColorSpace();
330
331 if (d->displayColorConverter->canSkipDisplayConversion(generationColorSpace)) {
332 half *pixelPtr = image.data();
333
334 for (int y = 0; y < image.height(); y++) {
335 for (int x = 0; x < image.width(); x++) {
336 Imf::Rgba &pxl = reinterpret_cast<Imf::Rgba &>(*pixelPtr);
337
338 float r, g, b;
339 FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y,
340 &r, &g, &b);
341
342 pxl.r = r * rangeCoeff;
343 pxl.g = g * rangeCoeff;
344 pxl.b = b * rangeCoeff;
345 pxl.a = 1.0;
346
347 pixelPtr += 4;
348 }
349 }
350
351 } else {
353
354 KisFixedPaintDeviceSP device = new KisFixedPaintDevice(generationColorSpace);
355 device->setRect(QRect(QPoint(), image.size()));
357 float *devicePtr = reinterpret_cast<float*>(device->data());
358
359 for (int y = 0; y < image.height(); y++) {
360 for (int x = 0; x < image.width(); x++) {
361 FillPolicy::getRGB(d->hue, xPortionCoeff, yPortionCoeff, x, y,
362 devicePtr, devicePtr + 1, devicePtr + 2);
363
364 devicePtr[0] *= rangeCoeff;
365 devicePtr[1] *= rangeCoeff;
366 devicePtr[2] *= rangeCoeff;
367 devicePtr[3] = 1.0;
368
369 devicePtr += 4;
370 }
371 }
372
373 // TODO: should we add caching for this space fetching?
374 const KoColorSpace *dstColorSpace =
379
380 d->displayColorConverter->applyDisplayFilteringF32(device, dstColorSpace);
381
382 // the output device must have float16/rgba space
384
385 memcpy(image.data(),
386 device->data(),
387 device->pixelSize() * device->bounds().width() * device->bounds().height());
388 }
389
390 widget->loadImage(image);
391}
392
394{
395 uploadPaletteData<FillHPolicy>(d->hueWidget, QSize(d->hueWidget->width(), d->huePreferredHeight));
396}
397
399{
400 const int maxSize = 256;
401 QSize newSize = d->valueWidget->size();
402 newSize.rwidth() = qMin(maxSize, newSize.width());
403 newSize.rheight() = qMin(maxSize, newSize.height());
404
405 uploadPaletteData<FillSVPolicy>(d->valueWidget, newSize);
406}
407
409{
410 const qreal newHue = pos.x();
411
412 if (!qFuzzyCompare(newHue, d->hue)) {
413 setHue(newHue);
414 }
415}
416
418{
419 const qreal newSaturation = pos.x();
420 const qreal newValue = 1.0 - pos.y();
421
422 if (!qFuzzyCompare(newSaturation, d->saturation) ||
423 !qFuzzyCompare(newValue, d->value)) {
424
425 setHSV(d->hue, newSaturation, newValue);
426 }
427}
428
430{
431 d->dynamicRangeCompressor->start(maxLuminance);
432}
433
435{
436 const qreal oldRange = d->currentRelativeDynamicRange;
437 const qreal newRange = qreal(maxLuminance) / d->currenOutputReferenceWhite;
438
439 if (qFuzzyCompare(oldRange, newRange)) return;
440
441 float r, g, b;
442 float denormHue = d->hue * 360.0;
443 float saturation = d->saturation;
444 float value = d->value;
445
446 HSVToRGB(denormHue, saturation, value, &r, &g, &b);
447
448 const qreal transformCoeff = oldRange / newRange;
449
450 r = qBound(0.0, r * transformCoeff, 1.0);
451 g = qBound(0.0, g * transformCoeff, 1.0);
452 b = qBound(0.0, b * transformCoeff, 1.0);
453
454 RGBToHSV(r, g, b, &denormHue, &saturation, &value);
455
456 d->currentRelativeDynamicRange = newRange;
458 setHSV(denormHue / 360.0, saturation, value, false);
459 d->hueWidget->setNormalizedPos(QPointF(denormHue / 360.0, 0));
460 d->valueWidget->setNormalizedPos(QPointF(saturation, 1.0 - value));
461}
462
464{
466
467 if (!converter) {
469 }
470
471 d->displayColorConverter = converter;
472
475 d->displayColorConverter, SIGNAL(displayConfigurationChanged()),
476 this, SLOT(slotDisplayConfigurationChanged()));
477 }
478
480}
481
483{
484 d->hasHDR = false;
485
486 if (d->hasHardwareHDR) {
488
494
495 if (d->hasHDR) {
496 const KoColorSpace *outputColorSpace = d->outputColorSpace();
497 if (!outputColorSpace->profile()->hdrReferenceWhite()) {
498 qWarning() << "WARNING: KisSmallColorWidget::slotDisplayConfigurationChanged(): output color space is "
499 "not a PQ space, the meaning of the range slider may be wrong!";
500 }
501
502 d->currenOutputReferenceWhite = outputColorSpace->profile()->hdrReferenceWhite().value_or(80.0);
503 }
504 }
505
506 if (d->dynamicRange) {
507 d->dynamicRange->setEnabled(d->hasHDR);
508 }
511
513 // TODO: also set the currently selected color again
514}
515
517{
518 d->updateAllowed = false;
519
520 float r, g, b;
521 HSVToRGB(d->hue * 360.0, d->saturation, d->value, &r, &g, &b);
522
523 if (d->hasHDR) {
524 const float rangeCoeff = d->effectiveRelativeDynamicRange();
525
526 r *= rangeCoeff;
527 g *= rangeCoeff;
528 b *= rangeCoeff;
529 }
530
531 const KoColorSpace *cs = d->generationColorSpace();
533
534 QVector<float> values(4);
535
537 values[0] = b;
538 values[1] = g;
539 values[2] = r;
540 values[3] = 1.0f;
541 } else {
542 values[0] = r;
543 values[1] = g;
544 values[2] = b;
545 values[3] = 1.0f;
546 }
547
548 KoColor c(cs);
549 cs->fromNormalisedChannelsValue(c.data(), values);
550 Q_EMIT colorChanged(c);
551
552 d->updateAllowed = true;
553}
554
555void KisSmallColorWidget::resizeEvent(QResizeEvent * event)
556{
557 QWidget::resizeEvent(event);
558 update();
560}
561
float value(const T *src, size_t ch)
qreal v
void RGBToHSV(float r, float g, float b, float *h, float *s, float *v)
void HSVToRGB(float h, float s, float v, float *r, float *g, float *b)
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float64BitsColorDepthID("F64", ki18n("64-bit float/channel"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
@ TRC_ITU_R_BT_2100_0_PQ
void getRGB(TReal &r, TReal &g, TReal &b, TReal hue)
void setHandlePaintingStrategy(HandlePaintingStrategy *strategy)
void setNormalizedPos(const QPointF &pos, bool update=true)
const KoColorSpace * paintingColorSpace() const
static KisDisplayColorConverter * dumbConverterInstance()
bool canSkipDisplayConversion(const KoColorSpace *cs) const
void applyDisplayFilteringF32(KisFixedPaintDeviceSP device, const KoColorSpace *dstColorSpace) const
const KoColorProfile * profile
void setRect(const QRect &rc)
int height() const
QSize size() const
int width() const
void loadImage(const KisGLImageF16 &image)
static KisOpenGLModeProber * instance()
const KoColorProfile * rootSurfaceColorProfile() const
static KisPlatformPluginInterfaceFactory * instance()
const KoColorProfile * rootSurfaceProfile() const
void sigRootSurfaceProfileChanged(const KoColorProfile *profile) const
void addConnection(Sender sender, Signal signal, Receiver receiver, Method method, Qt::ConnectionType type=Qt::AutoConnection)
This class is a spinbox in which you can click and drag to set the value. A slider like bar is displa...
void setExponentRatio(qreal newExponentRatio)
Set the exponent used by a power function to modify the values as a function of the horizontal positi...
void setValue(int newValue)
void setRange(int newMinimum, int newMaximum, bool computeNewFastSliderStep=true)
Set the minimum and the maximum values of the range, computing a new "fast slider step" based on the ...
void setPageStep(int newPageStep)
Does nothing currently.
void resizeEvent(QResizeEvent *event) override
void colorChanged(const KoColor &)
void slotHueSliderChanged(const QPointF &pos)
void setDisplayColorConverter(KisDisplayColorConverter *converter)
KisSmallColorWidget(QWidget *parent)
void slotInitiateUpdateDynamicRange(int maxLuminance)
void slotValueSliderChanged(const QPointF &pos)
void setHSV(qreal h, qreal s, qreal v, bool notifyChanged=true)
void setColor(const KoColor &color)
void updateDynamicRange(int maxLuminance)
void uploadPaletteData(KisGLImageWidget *widget, const QSize &size)
virtual KoID colorModelId() 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
virtual const KoColorProfile * profile() const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
quint8 * data()
Definition KoColor.h:144
QString id() const
Definition KoID.cpp:63
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
const KoColorSpace * outputColorSpace()
KisSignalAutoConnectionsStore colorConverterConnections
const KoColorSpace * generationColorSpace()
KisSignalCompressor * repaintCompressor
KisClickableGLImageWidget * hueWidget
KisSignalCompressor * resizeUpdateCompressor
KisSignalCompressor * valueSliderUpdateCompressor
KisDisplayColorConverter * displayColorConverter
const KoColorProfile * outputColorProfile()
QScopedPointer< KisSignalCompressorWithParam< int > > dynamicRangeCompressor
KisSignalCompressor * colorChangedSignalCompressor
KisClickableGLImageWidget * valueWidget
virtual std::optional< double > hdrReferenceWhite() const =0
hdrReferenceWhite HDR reference white is only available for Perceptual Quantizer profiles that save t...
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709SRGBProfile() const
const KoColorProfile * p2020G10Profile() const