1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qcolorspace.h"
5#include "qcolorspace_p.h"
6
7#include "qcolortransform.h"
8#include "qcolormatrix_p.h"
9#include "qcolortransferfunction_p.h"
10#include "qcolortransform_p.h"
11#include "qicc_p.h"
12
13#include <qatomic.h>
14#include <qmath.h>
15#include <qtransform.h>
16
17#include <qdebug.h>
18
19QT_BEGIN_NAMESPACE
20
21Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock;
22
23Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {};
24static void cleanupPredefinedColorspaces()
25{
26 for (QAtomicPointer<QColorSpacePrivate> &ptr : s_predefinedColorspacePrivates) {
27 QColorSpacePrivate *prv = ptr.fetchAndStoreAcquire(newValue: nullptr);
28 if (prv && !prv->ref.deref())
29 delete prv;
30 }
31}
32
33Q_DESTRUCTOR_FUNCTION(cleanupPredefinedColorspaces)
34
35QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries)
36{
37 switch (primaries) {
38 case QColorSpace::Primaries::SRgb:
39 redPoint = QPointF(0.640, 0.330);
40 greenPoint = QPointF(0.300, 0.600);
41 bluePoint = QPointF(0.150, 0.060);
42 whitePoint = QColorVector::D65Chromaticity();
43 break;
44 case QColorSpace::Primaries::DciP3D65:
45 redPoint = QPointF(0.680, 0.320);
46 greenPoint = QPointF(0.265, 0.690);
47 bluePoint = QPointF(0.150, 0.060);
48 whitePoint = QColorVector::D65Chromaticity();
49 break;
50 case QColorSpace::Primaries::AdobeRgb:
51 redPoint = QPointF(0.640, 0.330);
52 greenPoint = QPointF(0.210, 0.710);
53 bluePoint = QPointF(0.150, 0.060);
54 whitePoint = QColorVector::D65Chromaticity();
55 break;
56 case QColorSpace::Primaries::ProPhotoRgb:
57 redPoint = QPointF(0.7347, 0.2653);
58 greenPoint = QPointF(0.1596, 0.8404);
59 bluePoint = QPointF(0.0366, 0.0001);
60 whitePoint = QColorVector::D50Chromaticity();
61 break;
62 default:
63 Q_UNREACHABLE();
64 }
65}
66
67bool QColorSpacePrimaries::areValid() const
68{
69 if (!QColorVector::isValidChromaticity(chr: redPoint))
70 return false;
71 if (!QColorVector::isValidChromaticity(chr: greenPoint))
72 return false;
73 if (!QColorVector::isValidChromaticity(chr: bluePoint))
74 return false;
75 if (!QColorVector::isValidChromaticity(chr: whitePoint))
76 return false;
77 return true;
78}
79
80QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
81{
82 // This converts to XYZ in some undefined scale.
83 QColorMatrix toXyz = { .r: QColorVector(redPoint),
84 .g: QColorVector(greenPoint),
85 .b: QColorVector(bluePoint) };
86
87 // Since the white point should be (1.0, 1.0, 1.0) in the
88 // input, we can figure out the scale by using the
89 // inverse conversion on the white point.
90 QColorVector wXyz(whitePoint);
91 QColorVector whiteScale = toXyz.inverted().map(c: wXyz);
92
93 // Now we have scaled conversion to XYZ relative to the given whitepoint
94 toXyz = toXyz * QColorMatrix::fromScale(v: whiteScale);
95
96 // But we want a conversion to XYZ relative to D50
97 QColorVector wXyzD50 = QColorVector::D50();
98
99 if (wXyz != wXyzD50) {
100 // Do chromatic adaptation to map our white point to XYZ D50.
101
102 // The Bradford method chromatic adaptation matrix:
103 QColorMatrix abrad = { .r: { 0.8951f, -0.7502f, 0.0389f },
104 .g: { 0.2664f, 1.7135f, -0.0685f },
105 .b: { -0.1614f, 0.0367f, 1.0296f } };
106 QColorMatrix abradinv = { .r: { 0.9869929f, 0.4323053f, -0.0085287f },
107 .g: { -0.1470543f, 0.5183603f, 0.0400428f },
108 .b: { 0.1599627f, 0.0492912f, 0.9684867f } };
109
110 QColorVector srcCone = abrad.map(c: wXyz);
111 QColorVector dstCone = abrad.map(c: wXyzD50);
112
113 if (srcCone.x && srcCone.y && srcCone.z) {
114 QColorMatrix wToD50 = { .r: { dstCone.x / srcCone.x, 0, 0 },
115 .g: { 0, dstCone.y / srcCone.y, 0 },
116 .b: { 0, 0, dstCone.z / srcCone.z } };
117
118
119 QColorMatrix chromaticAdaptation = abradinv * (wToD50 * abrad);
120 toXyz = chromaticAdaptation * toXyz;
121 } else {
122 toXyz.r = {0, 0, 0}; // set to invalid value
123 }
124 }
125
126 return toXyz;
127}
128
129QColorSpacePrivate::QColorSpacePrivate()
130{
131}
132
133QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace)
134 : namedColorSpace(namedColorSpace)
135{
136 switch (namedColorSpace) {
137 case QColorSpace::SRgb:
138 primaries = QColorSpace::Primaries::SRgb;
139 transferFunction = QColorSpace::TransferFunction::SRgb;
140 description = QStringLiteral("sRGB");
141 break;
142 case QColorSpace::SRgbLinear:
143 primaries = QColorSpace::Primaries::SRgb;
144 transferFunction = QColorSpace::TransferFunction::Linear;
145 description = QStringLiteral("Linear sRGB");
146 break;
147 case QColorSpace::AdobeRgb:
148 primaries = QColorSpace::Primaries::AdobeRgb;
149 transferFunction = QColorSpace::TransferFunction::Gamma;
150 gamma = 2.19921875f; // Not quite 2.2, see https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
151 description = QStringLiteral("Adobe RGB");
152 break;
153 case QColorSpace::DisplayP3:
154 primaries = QColorSpace::Primaries::DciP3D65;
155 transferFunction = QColorSpace::TransferFunction::SRgb;
156 description = QStringLiteral("Display P3");
157 break;
158 case QColorSpace::ProPhotoRgb:
159 primaries = QColorSpace::Primaries::ProPhotoRgb;
160 transferFunction = QColorSpace::TransferFunction::ProPhotoRgb;
161 description = QStringLiteral("ProPhoto RGB");
162 break;
163 default:
164 Q_UNREACHABLE();
165 }
166 initialize();
167}
168
169QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma)
170 : primaries(primaries)
171 , transferFunction(transferFunction)
172 , gamma(gamma)
173{
174 identifyColorSpace();
175 initialize();
176}
177
178QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
179 QColorSpace::TransferFunction transferFunction,
180 float gamma)
181 : primaries(QColorSpace::Primaries::Custom)
182 , transferFunction(transferFunction)
183 , gamma(gamma)
184{
185 Q_ASSERT(primaries.areValid());
186 toXyz = primaries.toXyzMatrix();
187 whitePoint = QColorVector(primaries.whitePoint);
188 identifyColorSpace();
189 setTransferFunction();
190}
191
192QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QList<uint16_t> &transferFunctionTable)
193 : primaries(primaries)
194 , transferFunction(QColorSpace::TransferFunction::Custom)
195 , gamma(0)
196{
197 setTransferFunctionTable(transferFunctionTable);
198 identifyColorSpace();
199 initialize();
200}
201
202QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QList<uint16_t> &transferFunctionTable)
203 : primaries(QColorSpace::Primaries::Custom)
204 , transferFunction(QColorSpace::TransferFunction::Custom)
205 , gamma(0)
206{
207 Q_ASSERT(primaries.areValid());
208 toXyz = primaries.toXyzMatrix();
209 whitePoint = QColorVector(primaries.whitePoint);
210 setTransferFunctionTable(transferFunctionTable);
211 identifyColorSpace();
212 initialize();
213}
214
215QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
216 const QList<uint16_t> &redTransferFunctionTable,
217 const QList<uint16_t> &greenTransferFunctionTable,
218 const QList<uint16_t> &blueTransferFunctionTable)
219 : primaries(QColorSpace::Primaries::Custom)
220 , transferFunction(QColorSpace::TransferFunction::Custom)
221 , gamma(0)
222{
223 Q_ASSERT(primaries.areValid());
224 toXyz = primaries.toXyzMatrix();
225 whitePoint = QColorVector(primaries.whitePoint);
226 setTransferFunctionTables(redTransferFunctionTable,
227 greenTransferFunctionTable,
228 blueTransferFunctionTable);
229 identifyColorSpace();
230 setToXyzMatrix();
231}
232
233void QColorSpacePrivate::identifyColorSpace()
234{
235 switch (primaries) {
236 case QColorSpace::Primaries::SRgb:
237 if (transferFunction == QColorSpace::TransferFunction::SRgb) {
238 namedColorSpace = QColorSpace::SRgb;
239 if (description.isEmpty())
240 description = QStringLiteral("sRGB");
241 return;
242 }
243 if (transferFunction == QColorSpace::TransferFunction::Linear) {
244 namedColorSpace = QColorSpace::SRgbLinear;
245 if (description.isEmpty())
246 description = QStringLiteral("Linear sRGB");
247 return;
248 }
249 break;
250 case QColorSpace::Primaries::AdobeRgb:
251 if (transferFunction == QColorSpace::TransferFunction::Gamma) {
252 if (qAbs(t: gamma - 2.19921875f) < (1/1024.0f)) {
253 namedColorSpace = QColorSpace::AdobeRgb;
254 if (description.isEmpty())
255 description = QStringLiteral("Adobe RGB");
256 return;
257 }
258 }
259 break;
260 case QColorSpace::Primaries::DciP3D65:
261 if (transferFunction == QColorSpace::TransferFunction::SRgb) {
262 namedColorSpace = QColorSpace::DisplayP3;
263 if (description.isEmpty())
264 description = QStringLiteral("Display P3");
265 return;
266 }
267 break;
268 case QColorSpace::Primaries::ProPhotoRgb:
269 if (transferFunction == QColorSpace::TransferFunction::ProPhotoRgb) {
270 namedColorSpace = QColorSpace::ProPhotoRgb;
271 if (description.isEmpty())
272 description = QStringLiteral("ProPhoto RGB");
273 return;
274 }
275 if (transferFunction == QColorSpace::TransferFunction::Gamma) {
276 // ProPhoto RGB's curve is effectively gamma 1.8 for 8bit precision.
277 if (qAbs(t: gamma - 1.8f) < (1/1024.0f)) {
278 namedColorSpace = QColorSpace::ProPhotoRgb;
279 if (description.isEmpty())
280 description = QStringLiteral("ProPhoto RGB");
281 return;
282 }
283 }
284 break;
285 default:
286 break;
287 }
288
289 namedColorSpace = Unknown;
290}
291
292void QColorSpacePrivate::initialize()
293{
294 setToXyzMatrix();
295 setTransferFunction();
296}
297
298void QColorSpacePrivate::setToXyzMatrix()
299{
300 if (primaries == QColorSpace::Primaries::Custom) {
301 toXyz = QColorMatrix();
302 whitePoint = QColorVector::D50();
303 return;
304 }
305 QColorSpacePrimaries colorSpacePrimaries(primaries);
306 toXyz = colorSpacePrimaries.toXyzMatrix();
307 whitePoint = QColorVector(colorSpacePrimaries.whitePoint);
308}
309
310void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transferFunctionTable)
311{
312 QColorTransferTable table(transferFunctionTable.size(), transferFunctionTable);
313 if (!table.isEmpty() && !table.checkValidity()) {
314 qWarning() << "Invalid transfer function table given to QColorSpace";
315 trc[0].m_type = QColorTrc::Type::Uninitialized;
316 return;
317 }
318 transferFunction = QColorSpace::TransferFunction::Custom;
319 QColorTransferFunction curve;
320 if (table.asColorTransferFunction(transferFn: &curve)) {
321 // Table recognized as a specific curve
322 if (curve.isLinear()) {
323 transferFunction = QColorSpace::TransferFunction::Linear;
324 gamma = 1.0f;
325 } else if (curve.isSRgb()) {
326 transferFunction = QColorSpace::TransferFunction::SRgb;
327 }
328 trc[0].m_type = QColorTrc::Type::Function;
329 trc[0].m_fun = curve;
330 } else {
331 trc[0].m_type = QColorTrc::Type::Table;
332 trc[0].m_table = table;
333 }
334}
335
336void QColorSpacePrivate::setTransferFunctionTables(const QList<uint16_t> &redTransferFunctionTable,
337 const QList<uint16_t> &greenTransferFunctionTable,
338 const QList<uint16_t> &blueTransferFunctionTable)
339{
340 QColorTransferTable redTable(redTransferFunctionTable.size(), redTransferFunctionTable);
341 QColorTransferTable greenTable(greenTransferFunctionTable.size(), greenTransferFunctionTable);
342 QColorTransferTable blueTable(blueTransferFunctionTable.size(), blueTransferFunctionTable);
343 if (!redTable.isEmpty() && !greenTable.isEmpty() && !blueTable.isEmpty() &&
344 !redTable.checkValidity() && !greenTable.checkValidity() && !blueTable.checkValidity()) {
345 qWarning() << "Invalid transfer function table given to QColorSpace";
346 trc[0].m_type = QColorTrc::Type::Uninitialized;
347 trc[1].m_type = QColorTrc::Type::Uninitialized;
348 trc[2].m_type = QColorTrc::Type::Uninitialized;
349 return;
350 }
351 transferFunction = QColorSpace::TransferFunction::Custom;
352 QColorTransferFunction curve;
353 if (redTable.asColorTransferFunction(transferFn: &curve)) {
354 trc[0].m_type = QColorTrc::Type::Function;
355 trc[0].m_fun = curve;
356 } else {
357 trc[0].m_type = QColorTrc::Type::Table;
358 trc[0].m_table = redTable;
359 }
360 if (greenTable.asColorTransferFunction(transferFn: &curve)) {
361 trc[1].m_type = QColorTrc::Type::Function;
362 trc[1].m_fun = curve;
363 } else {
364 trc[1].m_type = QColorTrc::Type::Table;
365 trc[1].m_table = greenTable;
366 }
367 if (blueTable.asColorTransferFunction(transferFn: &curve)) {
368 trc[2].m_type = QColorTrc::Type::Function;
369 trc[2].m_fun = curve;
370 } else {
371 trc[2].m_type = QColorTrc::Type::Table;
372 trc[2].m_table = blueTable;
373 }
374 lut.generated.storeRelease(newValue: 0);
375}
376
377void QColorSpacePrivate::setTransferFunction()
378{
379 switch (transferFunction) {
380 case QColorSpace::TransferFunction::Linear:
381 trc[0].m_type = QColorTrc::Type::Function;
382 trc[0].m_fun = QColorTransferFunction();
383 if (qFuzzyIsNull(f: gamma))
384 gamma = 1.0f;
385 break;
386 case QColorSpace::TransferFunction::Gamma:
387 trc[0].m_type = QColorTrc::Type::Function;
388 trc[0].m_fun = QColorTransferFunction::fromGamma(gamma);
389 break;
390 case QColorSpace::TransferFunction::SRgb:
391 trc[0].m_type = QColorTrc::Type::Function;
392 trc[0].m_fun = QColorTransferFunction::fromSRgb();
393 if (qFuzzyIsNull(f: gamma))
394 gamma = 2.31f;
395 break;
396 case QColorSpace::TransferFunction::ProPhotoRgb:
397 trc[0].m_type = QColorTrc::Type::Function;
398 trc[0].m_fun = QColorTransferFunction::fromProPhotoRgb();
399 if (qFuzzyIsNull(f: gamma))
400 gamma = 1.8f;
401 break;
402 case QColorSpace::TransferFunction::Custom:
403 break;
404 default:
405 Q_UNREACHABLE();
406 break;
407 }
408 trc[1] = trc[0];
409 trc[2] = trc[0];
410 lut.generated.storeRelease(newValue: 0);
411}
412
413QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpacePrivate *out) const
414{
415 Q_ASSERT(out);
416 QColorTransform combined;
417 auto ptr = new QColorTransformPrivate;
418 combined.d = ptr;
419 ptr->colorSpaceIn = this;
420 ptr->colorSpaceOut = out;
421 ptr->colorMatrix = out->toXyz.inverted() * toXyz;
422 if (ptr->isIdentity())
423 return QColorTransform();
424 return combined;
425}
426
427QColorTransform QColorSpacePrivate::transformationToXYZ() const
428{
429 QColorTransform transform;
430 auto ptr = new QColorTransformPrivate;
431 transform.d = ptr;
432 ptr->colorSpaceIn = this;
433 ptr->colorSpaceOut = this;
434 ptr->colorMatrix = toXyz;
435 return transform;
436}
437
438/*!
439 \class QColorSpace
440 \brief The QColorSpace class provides a color space abstraction.
441 \since 5.14
442
443 \ingroup painting
444 \ingroup appearance
445 \inmodule QtGui
446
447 Color values can be interpreted in different ways, and based on the interpretation
448 can live in different spaces. We call this \e {color spaces}.
449
450 QColorSpace provides access to creating several predefined color spaces and
451 can generate QColorTransforms for converting colors from one color space to
452 another.
453
454 QColorSpace can also represent color spaces defined by ICC profiles or embedded
455 in images, that do not otherwise fit the predefined color spaces.
456
457 A color space can generally speaking be conceived as a combination of set of primary
458 colors and a transfer function. The primaries defines the axes of the color space, and
459 the transfer function how values are mapped on the axes.
460 The primaries are defined by three primary colors that represent exactly how red, green,
461 and blue look in this particular color space, and a white color that represents where
462 and how bright pure white is. The range of colors expressible by the primary colors is
463 called the gamut, and a color space that can represent a wider range of colors is also
464 known as a wide-gamut color space.
465
466 The transfer function or gamma curve determines how each component in the
467 color space is encoded. These are used because human perception does not operate
468 linearly, and the transfer functions try to ensure that colors will seem evenly
469 spaced to human eyes.
470*/
471
472
473/*!
474 \enum QColorSpace::NamedColorSpace
475
476 Predefined color spaces.
477
478 \value SRgb The sRGB color space, which Qt operates in by default. It is a close approximation
479 of how most classic monitors operate, and a mode most software and hardware support.
480 \l{http://www.color.org/chardata/rgb/srgb.xalter}{ICC registration of sRGB}.
481 \value SRgbLinear The sRGB color space with linear gamma. Useful for gamma-corrected blending.
482 \value AdobeRgb The Adobe RGB color space is a classic wide-gamut color space, using a gamma of 2.2.
483 \l{http://www.color.org/chardata/rgb/adobergb.xalter}{ICC registration of Adobe RGB (1998)}
484 \value DisplayP3 A color-space using the primaries of DCI-P3, but with the whitepoint and transfer
485 function of sRGB. Common in modern wide-gamut screens.
486 \l{http://www.color.org/chardata/rgb/DCIP3.xalter}{ICC registration of DCI-P3}
487 \value ProPhotoRgb The Pro Photo RGB color space, also known as ROMM RGB is a very wide gamut color space.
488 \l{http://www.color.org/chardata/rgb/rommrgb.xalter}{ICC registration of ROMM RGB}
489*/
490
491/*!
492 \enum QColorSpace::Primaries
493
494 Predefined sets of primary colors.
495
496 \value Custom The primaries are undefined or does not match any predefined sets.
497 \value SRgb The sRGB primaries
498 \value AdobeRgb The Adobe RGB primaries
499 \value DciP3D65 The DCI-P3 primaries with the D65 whitepoint
500 \value ProPhotoRgb The ProPhoto RGB primaries with the D50 whitepoint
501*/
502
503/*!
504 \enum QColorSpace::TransferFunction
505
506 Predefined transfer functions or gamma curves.
507
508 \value Custom The custom or null transfer function
509 \value Linear The linear transfer functions
510 \value Gamma A transfer function that is a real gamma curve based on the value of gamma()
511 \value SRgb The sRGB transfer function, composed of linear and gamma parts
512 \value ProPhotoRgb The ProPhoto RGB transfer function, composed of linear and gamma parts
513*/
514
515/*!
516 \fn QColorSpace::QColorSpace()
517
518 Creates a new colorspace object that represents an undefined and invalid colorspace.
519 */
520
521/*!
522 Creates a new colorspace object that represents a \a namedColorSpace.
523 */
524QColorSpace::QColorSpace(NamedColorSpace namedColorSpace)
525{
526 if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::ProPhotoRgb) {
527 qWarning() << "QColorSpace attempted constructed from invalid QColorSpace::NamedColorSpace: " << int(namedColorSpace);
528 return;
529 }
530 // The defined namespaces start at 1:
531 auto &atomicRef = s_predefinedColorspacePrivates[static_cast<int>(namedColorSpace) - 1];
532 QColorSpacePrivate *cspriv = atomicRef.loadAcquire();
533 if (!cspriv) {
534 auto *tmp = new QColorSpacePrivate(namedColorSpace);
535 tmp->ref.ref();
536 if (atomicRef.testAndSetOrdered(expectedValue: nullptr, newValue: tmp, currentValue&: cspriv))
537 cspriv = tmp;
538 else
539 delete tmp;
540 }
541 d_ptr = cspriv;
542 Q_ASSERT(isValid());
543}
544
545/*!
546 Creates a custom color space with the primaries \a primaries, using the transfer function \a transferFunction and
547 optionally \a gamma.
548 */
549QColorSpace::QColorSpace(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma)
550 : d_ptr(new QColorSpacePrivate(primaries, transferFunction, gamma))
551{
552}
553
554/*!
555 Creates a custom color space with the primaries \a primaries, using a gamma transfer function of
556 \a gamma.
557 */
558QColorSpace::QColorSpace(QColorSpace::Primaries primaries, float gamma)
559 : d_ptr(new QColorSpacePrivate(primaries, TransferFunction::Gamma, gamma))
560{
561}
562
563/*!
564 Creates a custom color space with the primaries \a gamut, using a custom transfer function
565 described by \a transferFunctionTable.
566
567 The table should contain at least 2 values, and contain an monotonically increasing list
568 of values from 0 to 65535.
569
570 \since 6.1
571 */
572QColorSpace::QColorSpace(QColorSpace::Primaries gamut, const QList<uint16_t> &transferFunctionTable)
573 : d_ptr(new QColorSpacePrivate(gamut, transferFunctionTable))
574{
575}
576
577/*!
578 Creates a custom colorspace with a primaries based on the chromaticities of the primary colors \a whitePoint,
579 \a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma.
580 */
581QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
582 const QPointF &greenPoint, const QPointF &bluePoint,
583 QColorSpace::TransferFunction transferFunction, float gamma)
584{
585 QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint);
586 if (!primaries.areValid()) {
587 qWarning() << "QColorSpace attempted constructed from invalid primaries:" << whitePoint << redPoint << greenPoint << bluePoint;
588 return;
589 }
590 d_ptr = new QColorSpacePrivate(primaries, transferFunction, gamma);
591}
592
593/*!
594 Creates a custom color space with primaries based on the chromaticities of the primary colors \a whitePoint,
595 \a redPoint, \a greenPoint and \a bluePoint, and using the custom transfer function described by
596 \a transferFunctionTable.
597
598 \since 6.1
599 */
600QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
601 const QPointF &greenPoint, const QPointF &bluePoint,
602 const QList<uint16_t> &transferFunctionTable)
603 : d_ptr(new QColorSpacePrivate({whitePoint, redPoint, greenPoint, bluePoint}, transferFunctionTable))
604{
605}
606
607/*!
608 Creates a custom color space with primaries based on the chromaticities of the primary colors \a whitePoint,
609 \a redPoint, \a greenPoint and \a bluePoint, and using the custom transfer functions described by
610 \a redTransferFunctionTable, \a greenTransferFunctionTable, and \a blueTransferFunctionTable.
611
612 \since 6.1
613 */
614QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
615 const QPointF &greenPoint, const QPointF &bluePoint,
616 const QList<uint16_t> &redTransferFunctionTable,
617 const QList<uint16_t> &greenTransferFunctionTable,
618 const QList<uint16_t> &blueTransferFunctionTable)
619 : d_ptr(new QColorSpacePrivate({whitePoint, redPoint, greenPoint, bluePoint},
620 redTransferFunctionTable,
621 greenTransferFunctionTable,
622 blueTransferFunctionTable))
623{
624}
625
626QColorSpace::~QColorSpace() = default;
627
628QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QColorSpacePrivate)
629
630QColorSpace::QColorSpace(const QColorSpace &colorSpace) noexcept = default;
631
632/*! \fn void QColorSpace::swap(QColorSpace &other)
633
634 Swaps color space \a other with this color space. This operation is very fast and
635 never fails.
636*/
637
638/*!
639 Returns the predefined primaries of the color space
640 or \c primaries::Custom if it doesn't match any of them.
641*/
642QColorSpace::Primaries QColorSpace::primaries() const noexcept
643{
644 if (Q_UNLIKELY(!d_ptr))
645 return QColorSpace::Primaries::Custom;
646 return d_ptr->primaries;
647}
648
649/*!
650 Returns the predefined transfer function of the color space
651 or \c TransferFunction::Custom if it doesn't match any of them.
652
653 \sa gamma(), setTransferFunction(), withTransferFunction()
654*/
655QColorSpace::TransferFunction QColorSpace::transferFunction() const noexcept
656{
657 if (Q_UNLIKELY(!d_ptr))
658 return QColorSpace::TransferFunction::Custom;
659 return d_ptr->transferFunction;
660}
661
662/*!
663 Returns the gamma value of color spaces with \c TransferFunction::Gamma,
664 an approximate gamma value for other predefined color spaces, or
665 0.0 if no approximate gamma is known.
666
667 \sa transferFunction()
668*/
669float QColorSpace::gamma() const noexcept
670{
671 if (Q_UNLIKELY(!d_ptr))
672 return 0.0f;
673 return d_ptr->gamma;
674}
675
676/*!
677 Sets the transfer function to \a transferFunction and \a gamma.
678
679 \sa transferFunction(), gamma(), withTransferFunction()
680*/
681void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma)
682{
683 if (transferFunction == TransferFunction::Custom)
684 return;
685 if (!d_ptr) {
686 d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunction, gamma);
687 return;
688 }
689 if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma)
690 return;
691 detach();
692 d_ptr->description.clear();
693 d_ptr->transferFunction = transferFunction;
694 d_ptr->gamma = gamma;
695 d_ptr->identifyColorSpace();
696 d_ptr->setTransferFunction();
697}
698
699/*!
700 Sets the transfer function to \a transferFunctionTable.
701
702 \since 6.1
703 \sa withTransferFunction()
704*/
705void QColorSpace::setTransferFunction(const QList<uint16_t> &transferFunctionTable)
706{
707 if (!d_ptr) {
708 d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunctionTable);
709 d_ptr->ref.ref();
710 return;
711 }
712 detach();
713 d_ptr->description.clear();
714 d_ptr->setTransferFunctionTable(transferFunctionTable);
715 d_ptr->gamma = 0;
716 d_ptr->identifyColorSpace();
717 d_ptr->setTransferFunction();
718}
719
720/*!
721 Sets the transfer functions to \a redTransferFunctionTable,
722 \a greenTransferFunctionTable and \a blueTransferFunctionTable.
723
724 \since 6.1
725 \sa withTransferFunctions()
726*/
727void QColorSpace::setTransferFunctions(const QList<uint16_t> &redTransferFunctionTable,
728 const QList<uint16_t> &greenTransferFunctionTable,
729 const QList<uint16_t> &blueTransferFunctionTable)
730{
731 if (!d_ptr) {
732 d_ptr = new QColorSpacePrivate();
733 d_ptr->setTransferFunctionTables(redTransferFunctionTable,
734 greenTransferFunctionTable,
735 blueTransferFunctionTable);
736 d_ptr->ref.ref();
737 return;
738 }
739 detach();
740 d_ptr->description.clear();
741 d_ptr->setTransferFunctionTables(redTransferFunctionTable,
742 greenTransferFunctionTable,
743 blueTransferFunctionTable);
744 d_ptr->gamma = 0;
745 d_ptr->identifyColorSpace();
746}
747
748/*!
749 Returns a copy of this color space, except using the transfer function
750 \a transferFunction and \a gamma.
751
752 \sa transferFunction(), gamma(), setTransferFunction()
753*/
754QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) const
755{
756 if (!isValid() || transferFunction == QColorSpace::TransferFunction::Custom)
757 return *this;
758 if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma)
759 return *this;
760 QColorSpace out(*this);
761 out.setTransferFunction(transferFunction, gamma);
762 return out;
763}
764
765/*!
766 Returns a copy of this color space, except using the transfer function
767 described by \a transferFunctionTable.
768
769 \since 6.1
770 \sa transferFunction(), setTransferFunction()
771*/
772QColorSpace QColorSpace::withTransferFunction(const QList<uint16_t> &transferFunctionTable) const
773{
774 if (!isValid())
775 return *this;
776 QColorSpace out(*this);
777 out.setTransferFunction(transferFunctionTable);
778 return out;
779}
780
781/*!
782 Returns a copy of this color space, except using the transfer functions
783 described by \a redTransferFunctionTable, \a greenTransferFunctionTable and
784 \a blueTransferFunctionTable.
785
786 \since 6.1
787 \sa setTransferFunctions()
788*/
789QColorSpace QColorSpace::withTransferFunctions(const QList<uint16_t> &redTransferFunctionTable,
790 const QList<uint16_t> &greenTransferFunctionTable,
791 const QList<uint16_t> &blueTransferFunctionTable) const
792{
793 if (!isValid())
794 return *this;
795 QColorSpace out(*this);
796 out.setTransferFunctions(redTransferFunctionTable, greenTransferFunctionTable, blueTransferFunctionTable);
797 return out;
798}
799
800/*!
801 Sets the primaries to those of the \a primariesId set.
802
803 \sa primaries()
804*/
805void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId)
806{
807 if (primariesId == Primaries::Custom)
808 return;
809 if (!d_ptr) {
810 d_ptr = new QColorSpacePrivate(primariesId, TransferFunction::Custom, 0.0f);
811 return;
812 }
813 if (d_ptr->primaries == primariesId)
814 return;
815 detach();
816 d_ptr->description.clear();
817 d_ptr->primaries = primariesId;
818 d_ptr->identifyColorSpace();
819 d_ptr->setToXyzMatrix();
820}
821
822/*!
823 Set primaries to the chromaticities of \a whitePoint, \a redPoint, \a greenPoint
824 and \a bluePoint.
825
826 \sa primaries()
827*/
828void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoint,
829 const QPointF &greenPoint, const QPointF &bluePoint)
830{
831 QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint);
832 if (!primaries.areValid())
833 return;
834 if (!d_ptr) {
835 d_ptr = new QColorSpacePrivate(primaries, TransferFunction::Custom, 0.0f);
836 return;
837 }
838 QColorMatrix toXyz = primaries.toXyzMatrix();
839 if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz)
840 return;
841 detach();
842 d_ptr->description.clear();
843 d_ptr->primaries = QColorSpace::Primaries::Custom;
844 d_ptr->toXyz = toXyz;
845 d_ptr->whitePoint = QColorVector(primaries.whitePoint);
846 d_ptr->identifyColorSpace();
847}
848
849/*!
850 \internal
851*/
852void QColorSpace::detach()
853{
854 if (d_ptr)
855 d_ptr.detach();
856 else
857 d_ptr = new QColorSpacePrivate;
858}
859
860/*!
861 Returns an ICC profile representing the color space.
862
863 If the color space was generated from an ICC profile, that profile
864 is returned, otherwise one is generated.
865
866 \note Even invalid color spaces may return the ICC profile if they
867 were generated from one, to allow applications to implement wider
868 support themselves.
869
870 \sa fromIccProfile()
871*/
872QByteArray QColorSpace::iccProfile() const
873{
874 if (Q_UNLIKELY(!d_ptr))
875 return QByteArray();
876 if (!d_ptr->iccProfile.isEmpty())
877 return d_ptr->iccProfile;
878 if (!isValid())
879 return QByteArray();
880 return QIcc::toIccProfile(space: *this);
881}
882
883/*!
884 Creates a QColorSpace from ICC profile \a iccProfile.
885
886 \note Not all ICC profiles are supported. QColorSpace only supports
887 RGB-XYZ ICC profiles that are three-component matrix-based.
888
889 If the ICC profile is not supported an invalid QColorSpace is returned
890 where you can still read the original ICC profile using iccProfile().
891
892 \sa iccProfile()
893*/
894QColorSpace QColorSpace::fromIccProfile(const QByteArray &iccProfile)
895{
896 QColorSpace colorSpace;
897 if (QIcc::fromIccProfile(data: iccProfile, colorSpace: &colorSpace))
898 return colorSpace;
899 colorSpace.detach();
900 colorSpace.d_ptr->iccProfile = iccProfile;
901 return colorSpace;
902}
903
904/*!
905 Returns \c true if the color space is valid.
906*/
907bool QColorSpace::isValid() const noexcept
908{
909 return d_ptr
910 && d_ptr->toXyz.isValid()
911 && d_ptr->trc[0].isValid() && d_ptr->trc[1].isValid() && d_ptr->trc[2].isValid();
912}
913
914/*!
915 \fn bool QColorSpace::operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
916
917 Returns \c true if colorspace \a colorSpace1 is equal to colorspace \a colorSpace2;
918 otherwise returns \c false
919*/
920
921/*!
922 \fn bool QColorSpace::operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
923
924 Returns \c true if colorspace \a colorSpace1 is not equal to colorspace \a colorSpace2;
925 otherwise returns \c false
926*/
927
928/*!
929 \internal
930*/
931bool QColorSpace::equals(const QColorSpace &other) const
932{
933 if (d_ptr == other.d_ptr)
934 return true;
935 if (!d_ptr || !other.d_ptr)
936 return false;
937
938 if (d_ptr->namedColorSpace && other.d_ptr->namedColorSpace)
939 return d_ptr->namedColorSpace == other.d_ptr->namedColorSpace;
940
941 const bool valid1 = isValid();
942 const bool valid2 = other.isValid();
943 if (valid1 != valid2)
944 return false;
945 if (!valid1 && !valid2) {
946 if (!d_ptr->iccProfile.isEmpty() || !other.d_ptr->iccProfile.isEmpty())
947 return d_ptr->iccProfile == other.d_ptr->iccProfile;
948 }
949
950 // At this point one or both color spaces are unknown, and must be compared in detail instead
951
952 if (primaries() != QColorSpace::Primaries::Custom && other.primaries() != QColorSpace::Primaries::Custom) {
953 if (primaries() != other.primaries())
954 return false;
955 } else {
956 if (d_ptr->toXyz != other.d_ptr->toXyz)
957 return false;
958 }
959
960 if (transferFunction() != QColorSpace::TransferFunction::Custom &&
961 other.transferFunction() != QColorSpace::TransferFunction::Custom) {
962 if (transferFunction() != other.transferFunction())
963 return false;
964 if (transferFunction() == QColorSpace::TransferFunction::Gamma)
965 return (qAbs(t: gamma() - other.gamma()) <= (1.0f / 512.0f));
966 return true;
967 }
968
969 if (d_ptr->trc[0] != other.d_ptr->trc[0] ||
970 d_ptr->trc[1] != other.d_ptr->trc[1] ||
971 d_ptr->trc[2] != other.d_ptr->trc[2])
972 return false;
973
974 return true;
975}
976
977/*!
978 Generates and returns a color space transformation from this color space to
979 \a colorspace.
980*/
981QColorTransform QColorSpace::transformationToColorSpace(const QColorSpace &colorspace) const
982{
983 if (!isValid() || !colorspace.isValid())
984 return QColorTransform();
985
986 if (*this == colorspace)
987 return QColorTransform();
988
989 return d_ptr->transformationToColorSpace(out: colorspace.d_ptr.get());
990}
991
992/*!
993 Returns the color space as a QVariant.
994 \since 5.15
995*/
996QColorSpace::operator QVariant() const
997{
998 return QVariant::fromValue(value: *this);
999}
1000
1001/*!
1002 Returns the name or short description. If a description hasn't been given
1003 in setDescription(), the original name of the profile is returned if the
1004 profile is unmodified, a guessed name is returned if the profile has been
1005 recognized as a known color space, otherwise an empty string is returned.
1006
1007 \since 6.2
1008*/
1009QString QColorSpace::description() const noexcept
1010{
1011 if (d_ptr)
1012 return d_ptr->userDescription.isEmpty() ? d_ptr->description : d_ptr->userDescription;
1013 return QString();
1014}
1015
1016/*!
1017 Sets the name or short description of the color space to \a description.
1018
1019 If set to empty description() will return original or guessed descriptions
1020 instead.
1021
1022 \since 6.2
1023*/
1024void QColorSpace::setDescription(const QString &description)
1025{
1026 detach();
1027 d_ptr->userDescription = description;
1028}
1029
1030/*****************************************************************************
1031 QColorSpace stream functions
1032 *****************************************************************************/
1033#if !defined(QT_NO_DATASTREAM)
1034/*!
1035 \fn QDataStream &operator<<(QDataStream &stream, const QColorSpace &colorSpace)
1036 \relates QColorSpace
1037
1038 Writes the given \a colorSpace to the given \a stream as an ICC profile.
1039
1040 \sa QColorSpace::iccProfile(), {Serializing Qt Data Types}
1041*/
1042
1043QDataStream &operator<<(QDataStream &s, const QColorSpace &image)
1044{
1045 s << image.iccProfile();
1046 return s;
1047}
1048
1049/*!
1050 \fn QDataStream &operator>>(QDataStream &stream, QColorSpace &colorSpace)
1051 \relates QColorSpace
1052
1053 Reads a color space from the given \a stream and stores it in the given
1054 \a colorSpace.
1055
1056 \sa QColorSpace::fromIccProfile(), {Serializing Qt Data Types}
1057*/
1058
1059QDataStream &operator>>(QDataStream &s, QColorSpace &colorSpace)
1060{
1061 QByteArray iccProfile;
1062 s >> iccProfile;
1063 colorSpace = QColorSpace::fromIccProfile(iccProfile);
1064 return s;
1065}
1066#endif // QT_NO_DATASTREAM
1067
1068#ifndef QT_NO_DEBUG_STREAM
1069QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
1070{
1071 QDebugStateSaver saver(dbg);
1072 dbg.nospace();
1073 dbg << "QColorSpace(";
1074 if (colorSpace.d_ptr) {
1075 if (colorSpace.d_ptr->namedColorSpace)
1076 dbg << colorSpace.d_ptr->namedColorSpace << ", ";
1077 dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
1078 dbg << ", gamma=" << colorSpace.gamma();
1079 }
1080 dbg << ')';
1081 return dbg;
1082}
1083#endif
1084
1085QT_END_NAMESPACE
1086
1087#include "moc_qcolorspace.cpp"
1088

source code of qtbase/src/gui/painting/qcolorspace.cpp