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;
59 bool hasHDR = false;
60 bool hasHardwareHDR = false;
61
62#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
63 KisRootSurfaceInfoProxy *rootSurfaceInfoProxy = nullptr;
64#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
65
68 }
69
72
73 if (KisOpenGLModeProber::instance()->useHDRMode()) {
75#if KRITA_USE_SURFACE_COLOR_MANAGEMENT_API
76 } else if (KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS()) {
77 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(rootSurfaceInfoProxy, profile);
78 profile = rootSurfaceInfoProxy->rootSurfaceProfile();
79#endif /* KRITA_USE_SURFACE_COLOR_MANAGEMENT_API */
80 } else {
81 // we are a normal QWidget's surface
83 }
84
85 return profile;
86 }
87
95
98
99 if (!result || result->colorModelId() != RGBAColorModelID) {
100 result = outputColorSpace();
101 } else if (result && result->colorDepthId() != Float32BitsColorDepthID) {
103 colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), result->profile());
104 }
105
106 // PQ color space we delinearize into linear one
107 if (result
108 && result->colorModelId() == RGBAColorModelID
109 && 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);
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(80.0 * 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) / 80.0;
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
496 if (d->dynamicRange) {
497 d->dynamicRange->setEnabled(d->hasHDR);
498 }
501
503 // TODO: also set the currently selected color again
504}
505
507{
508 d->updateAllowed = false;
509
510 float r, g, b;
511 HSVToRGB(d->hue * 360.0, d->saturation, d->value, &r, &g, &b);
512
513 if (d->hasHDR) {
514 const float rangeCoeff = d->effectiveRelativeDynamicRange();
515
516 r *= rangeCoeff;
517 g *= rangeCoeff;
518 b *= rangeCoeff;
519 }
520
521 const KoColorSpace *cs = d->generationColorSpace();
523
524 QVector<float> values(4);
525
527 values[0] = b;
528 values[1] = g;
529 values[2] = r;
530 values[3] = 1.0f;
531 } else {
532 values[0] = r;
533 values[1] = g;
534 values[2] = b;
535 values[3] = 1.0f;
536 }
537
538 KoColor c(cs);
539 cs->fromNormalisedChannelsValue(c.data(), values);
540 Q_EMIT colorChanged(c);
541
542 d->updateAllowed = true;
543}
544
545void KisSmallColorWidget::resizeEvent(QResizeEvent * event)
546{
547 QWidget::resizeEvent(event);
548 update();
550}
551
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"))
void getRGB(TReal &r, TReal &g, TReal &b, TReal hue)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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 QByteArray uniqueId() const =0
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709SRGBProfile() const
const KoColorProfile * p2020PQProfile() const
const KoColorProfile * p2020G10Profile() const