Krita Source Code Documentation
Loading...
Searching...
No Matches
KisVisualColorModel.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-3.0-or-later
6 */
7
9
10#include <QVector>
11#include <QVector3D>
12#include <QVector4D>
13#include <QList>
14#include <QtMath>
15
16#include <KSharedConfig>
17#include <KConfigGroup>
18
19#include "KoColorConversions.h"
21#include "KoColorProfile.h"
22#include "KoChannelInfo.h"
26#include "kis_debug.h"
27
29{
32 bool exposureSupported {false};
33 bool isRGBA {false};
34 bool isLinear {false};
35 bool applyGamma {false};
36 bool allowUpdates {true};
37 int logicalToMemoryPosition[4]; // map logical channel index to storage index for display
39 qreal gamma {2.2};
40 qreal lumaRGB[3] {0.2126, 0.7152, 0.0722};
41 QVector4D channelValues;
46};
47
49 : QObject(0)
50 , m_d(new Private)
51{
52 KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
53 m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
54
55}
56
60
62{
63 if (!m_d->allowUpdates) {
64 return;
65 }
66
67 if (!m_d->currentCS) {
68 m_d->currentcolor = c;
70 }
71 else {
72 m_d->currentcolor = c.convertedTo(m_d->currentCS);
73 m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
75 }
76}
77
79{
80 if (!m_d->currentCS || *m_d->currentCS != *cs) {
81 const KoColorSpace *csNew = cs;
82
83 // PQ color space is not very suitable for color picking, substitute with linear one
87
91 }
92
93
94 // TODO: split off non-config related initializations
95 loadColorSpace(csNew);
96 m_d->currentcolor = KoColor(csNew);
97 m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
98 Q_EMIT sigColorSpaceChanged();
99 }
100}
101
102void KisVisualColorModel::slotSetChannelValues(const QVector4D &values)
103{
104 if (!m_d->allowUpdates) {
105 return;
106 }
107
108 quint32 changeFlags = 0;
109 QVector4D newValues(0, 0, 0, 0);
110 for (int i = 0; i < m_d->colorChannelCount; i++) {
111 newValues[i] = values[i];
112 changeFlags |= quint32(values[i] != m_d->channelValues[i]) << i;
113 }
114 if (changeFlags != 0) {
115 m_d->allowUpdates = false;
116 m_d->channelValues = newValues;
117 m_d->currentcolor = convertChannelValuesToKoColor(newValues);
118 Q_EMIT sigChannelValuesChanged(m_d->channelValues, changeFlags);
119 Q_EMIT sigNewColor(m_d->currentcolor);
120 m_d->allowUpdates = true;
121 }
122}
123
125{
126 return m_d->currentcolor;
127}
128
130{
131 return m_d->channelValues;
132}
133
135{
136 return m_d->colorChannelCount;
137}
138
143
145{
146 return m_d->channelMaxValues;
147}
148
149void KisVisualColorModel::setMaxChannelValues(const QVector4D &maxValues)
150{
151 if (maxValues == m_d->channelMaxValues) {
152 return;
153 }
154 m_d->channelMaxValues = maxValues;
155 if (m_d->exposureSupported) {
156 // need to re-scale our normalized channel values on exposure changes:
157 m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
159 }
160}
161
163{
164 m_d->channelMaxValues = other.m_d->channelMaxValues;
165 setRGBColorModel(other.m_d->modelRGB);
166 if (other.colorSpace()) {
169 }
170}
171
173{
174 if (model == m_d->modelRGB) {
175 return;
176 }
177 if (model >= ColorModel::HSV && model <= ColorModel::HSY) {
178 m_d->modelRGB = model;
179 if (m_d->isRGBA) {
180 m_d->model = model;
181 m_d->applyGamma = (m_d->isLinear && m_d->modelRGB != ColorModel::HSY);
182 Q_EMIT sigColorModelChanged();
183 m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
185 }
186 }
187}
188
190{
191 return m_d->currentCS;
192}
193
195{
196 return (m_d->model >= ColorModel::HSV && m_d->model <= ColorModel::HSY);
197}
198
200{
201 return (m_d->exposureSupported);
202}
203
205{
206 KoColor c(m_d->currentCS);
207 QVector4D baseValues(values);
208 QVector<float> channelVec(c.colorSpace()->channelCount());
209 channelVec.fill(1.0);
210
211 if (m_d->model != ColorModel::Channel && m_d->isRGBA) {
212
213 if (m_d->model == ColorModel::HSV) {
214 HSVToRGB(values.x()*360, values.y(), values.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
215 }
216 else if (m_d->model == ColorModel::HSL) {
217 HSLToRGB(values.x()*360, values.y(), values.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
218 }
219 else if (m_d->model == ColorModel::HSI) {
220 // why suddenly qreal?
221 qreal temp[3];
222 HSIToRGB(values.x(), values.y(), values.z(), &temp[0], &temp[1], &temp[2]);
223 baseValues.setX(temp[0]);
224 baseValues.setY(temp[1]);
225 baseValues.setZ(temp[2]);
226 }
227 else /*if (m_d->model == ColorModel::HSY)*/ {
228 qreal temp[3];
229 qreal Y = pow(values.z(), m_d->gamma);
230 HSYToRGB(values.x(), values.y(), Y, &temp[0], &temp[1], &temp[2],
231 m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
232 baseValues.setX(temp[0]);
233 baseValues.setY(temp[1]);
234 baseValues.setZ(temp[2]);
235 if (!m_d->isLinear) {
236 // Note: not all profiles define a TRC necessary for (de-)linearization,
237 // substituting with a linear profiles would be better
238 QVector<qreal> tempVec({baseValues[0], baseValues[1], baseValues[2]});
239 if (m_d->exposureSupported) {
240 m_d->currentCS->profile()->delinearizeFloatValue(tempVec);
241 }
242 else {
243 m_d->currentCS->profile()->delinearizeFloatValueFast(tempVec);
244 }
245 baseValues = QVector4D(tempVec[0], tempVec[1], tempVec[2], 0);
246 }
247 }
248 if (m_d->applyGamma) {
249 for (int i=0; i<3; i++) {
250 baseValues[i] = pow(baseValues[i], 2.2);
251 }
252 }
253 }
254
255 if (m_d->exposureSupported) {
256 for (int i = 0; i < m_d->colorChannelCount; i++) {
257 baseValues[i] *= m_d->channelMaxValues[i];
258 }
259 }
260
261 for (int i=0; i<m_d->colorChannelCount; i++) {
262 channelVec[m_d->logicalToMemoryPosition[i]] = baseValues[i];
263 }
264
265 c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelVec);
266
267 return c;
268
269}
270
272{
273 if (c.colorSpace() != m_d->currentCS) {
274 c.convertTo(m_d->currentCS);
275 }
276 QVector<float> channelVec (c.colorSpace()->channelCount());
277 channelVec.fill(1.0);
278 m_d->currentCS->normalisedChannelsValue(c.data(), channelVec);
279 QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0);
280
281 for (int i =0; i<m_d->colorChannelCount; i++) {
282 channelValuesDisplay[i] = channelVec[m_d->logicalToMemoryPosition[i]];
283 }
284
285 if (m_d->exposureSupported) {
286 for (int i = 0; i < m_d->colorChannelCount; i++) {
287 channelValuesDisplay[i] /= m_d->channelMaxValues[i];
288 }
289 }
290 if (m_d->model != ColorModel::Channel && m_d->isRGBA) {
291 if (m_d->applyGamma) {
292 for (int i=0; i<3; i++) {
293 channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2);
294 }
295 }
296 if (m_d->model == ColorModel::HSV) {
297 QVector3D hsv;
298 RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]);
299 hsv[0] /= 360;
300 coordinates = QVector4D(hsv, 0.f);
301 } else if (m_d->model == ColorModel::HSL) {
302 QVector3D hsl;
303 RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]);
304 hsl[0] /= 360;
305 coordinates = QVector4D(hsl, 0.f);
306 } else if (m_d->model == ColorModel::HSI) {
307 qreal hsi[3];
308 RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]);
309 coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f);
310 } else if (m_d->model == ColorModel::HSY) {
311 if (!m_d->isLinear) {
312 // Note: not all profiles define a TRC necessary for (de-)linearization,
313 // substituting with a linear profiles would be better
314 QVector<qreal> temp({channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2]});
315 m_d->currentCS->profile()->linearizeFloatValue(temp);
316 channelValuesDisplay = QVector4D(temp[0], temp[1], temp[2], 0);
317 }
318 qreal hsy[3];
319 RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2],
320 m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
321 hsy[2] = pow(hsy[2], 1/m_d->gamma);
322 coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f);
323 }
324 // if we couldn't determine a hue, keep last value
325 if (coordinates[0] < 0) {
326 coordinates[0] = m_d->channelValues[0];
327 }
328 for (int i=0; i<3; i++) {
329 coordinates[i] = qBound(0.f, coordinates[i], 1.f);
330 }
331 } else {
332 for (int i=0; i<4; i++) {
333 coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f);
334 }
335 }
336 return coordinates;
337}
338
340{
341 KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
342 m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
343
344 m_d->gamma = cfg.readEntry("gamma", 2.2);
345
347
348 switch(m_d->acs_config.mainTypeParameter) {
349
354 RGB_model = KisVisualColorModel::HSV;
355 break;
356
360 RGB_model = KisVisualColorModel::HSL;
361 break;
362
366 RGB_model = KisVisualColorModel::HSI;
367 break;
368
372 RGB_model = KisVisualColorModel::HSY;
373 break;
374
375 default:
377 }
378 if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
379 //Triangle only really works in HSV mode.
380 RGB_model = KisVisualColorModel::HSV;
381 }
382
383 setRGBColorModel(RGB_model);
384}
385
387{
388 const QList<KoChannelInfo *> channelList = cs->channels();
389 int cCount = 0;
390
391 for (int i = 0; i < channelList.size(); i++) {
392 const KoChannelInfo *channel = channelList.at(i);
393 if (channel->channelType() != KoChannelInfo::ALPHA) {
394 quint32 logical = channel->displayPosition();
395 if (logical > cs->alphaPos()) {
396 --logical;
397 }
398 m_d->logicalToMemoryPosition[logical] = i;
399 m_d->channelMaxValues[logical] = channel->getUIMax();
400 ++cCount;
401 }
402 }
403 KIS_ASSERT_X(cCount < 5, "", "unsupported channel count!");
404
405 m_d->colorChannelCount = cCount;
406
411 && cs->colorModelId() != CMYKAColorModelID) {
412 m_d->exposureSupported = true;
413 } else {
414 m_d->exposureSupported = false;
415 }
416 m_d->isRGBA = (cs->colorModelId() == RGBAColorModelID);
417
418 const KoColorProfile *profile = cs->profile();
419 m_d->isLinear = (profile && profile->isLinear());
420
421 if (m_d->isRGBA) {
422 m_d->applyGamma = (m_d->isLinear && m_d->modelRGB != ColorModel::HSY);
423 // Note: only profiles that define colorants will give precise luma coefficients.
424 // Maybe using the explicitly set values of the Advanced Color Selector is better?
425 QVector <qreal> luma = cs->lumaCoefficients();
426 memcpy(m_d->lumaRGB, luma.constData(), 3*sizeof(qreal));
427 m_d->model = m_d->modelRGB;
428 } else {
430 }
431
432 const KoColorSpace *oldCS = m_d->currentCS;
433 m_d->currentCS = cs;
434
435 if (!oldCS || (oldCS->colorModelId() != cs->colorModelId())) {
436 Q_EMIT sigColorModelChanged();
437 }
438}
439
441{
442 bool updatesAllowed = m_d->allowUpdates;
443 m_d->allowUpdates = false;
444 Q_EMIT sigChannelValuesChanged(m_d->channelValues, (1u << m_d->colorChannelCount) - 1);
445 m_d->allowUpdates = updatesAllowed;
446}
void HSYToRGB(const qreal h, const qreal s, const qreal y, qreal *red, qreal *green, qreal *blue, qreal R, qreal G, qreal B)
void RGBToHSV(float r, float g, float b, float *h, float *s, float *v)
void RGBToHSI(qreal r, qreal g, qreal b, qreal *h, qreal *s, qreal *i)
void HSIToRGB(const qreal h, const qreal s, const qreal i, qreal *red, qreal *green, qreal *blue)
void RGBToHSL(float r, float g, float b, float *h, float *s, float *l)
void RGBToHSY(const qreal r, const qreal g, const qreal b, qreal *h, qreal *s, qreal *y, qreal R, qreal G, qreal B)
void HSVToRGB(float h, float s, float v, float *r, float *g, float *b)
void HSLToRGB(float h, float sl, float l, 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 CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID LABAColorModelID("LABA", ki18n("L*a*b*/Alpha"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
static KisColorSelectorConfiguration fromString(QString string)
The KisVisualColorModel class allows manipulating a KoColor using various color models.
void setMaxChannelValues(const QVector4D &maxValues)
void sigColorModelChanged()
sigColorModelChanged is emitted whenever the color model changes.
const KoColorSpace * colorSpace() const
void copyState(const KisVisualColorModel &other)
Copy the internal state of another KisVisualColorModel.
void slotSetColorSpace(const KoColorSpace *cs)
void slotLoadACSConfig()
slotLoadACSConfig loads supported settings from Advanced Color Selector
QVector4D convertKoColorToChannelValues(KoColor c) const
QVector4D channelValues() const
QVector4D maxChannelValues() const
void loadColorSpace(const KoColorSpace *cs)
KoColor convertChannelValuesToKoColor(const QVector4D &values) const
void slotSetChannelValues(const QVector4D &values)
void setRGBColorModel(ColorModel model)
Set the HSX color model used for RGB color spaces.
void sigNewColor(const KoColor &c)
const QScopedPointer< Private > m_d
ColorModel colorModel() const
void slotSetColor(const KoColor &c)
void sigChannelValuesChanged(const QVector4D &values, quint32 channelFlags)
void sigColorSpaceChanged()
sigColorSpaceChanged notifies that the color space from which the channel values are derived changed,...
@ ALPHA
The channel represents the opacity of a pixel.
enumChannelType channelType() const
qint32 displayPosition() const
double getUIMax(void) const
virtual quint32 alphaPos() const =0
QList< KoChannelInfo * > channels
virtual KoID colorModelId() const =0
QVector< qreal > lumaCoefficients
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() 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
KoColor convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
Definition KoColor.cpp:163
quint8 * data()
Definition KoColor.h:144
const KoColorSpace * colorSpace() const
return the current colorSpace
Definition KoColor.h:82
QString id() const
Definition KoID.cpp:63
#define KIS_ASSERT_X(cond, where, what)
Definition kis_assert.h:40
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
KisColorSelectorConfiguration acs_config
virtual QByteArray uniqueId() const =0
virtual bool isLinear() const =0
static KoColorSpaceRegistry * instance()
const KoColorProfile * p2020PQProfile() const
const KoColorProfile * p2020G10Profile() const