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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock; |
22 | |
23 | Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {}; |
24 | static 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 | |
33 | Q_DESTRUCTOR_FUNCTION(cleanupPredefinedColorspaces) |
34 | |
35 | QColorSpacePrimaries::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 | |
67 | bool 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 | |
80 | QColorMatrix 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 | |
129 | QColorSpacePrivate::QColorSpacePrivate() |
130 | { |
131 | } |
132 | |
133 | QColorSpacePrivate::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 | |
169 | QColorSpacePrivate::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 | |
178 | QColorSpacePrivate::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 | |
192 | QColorSpacePrivate::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 | |
202 | QColorSpacePrivate::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 | |
215 | QColorSpacePrivate::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 | |
233 | void 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 | |
292 | void QColorSpacePrivate::initialize() |
293 | { |
294 | setToXyzMatrix(); |
295 | setTransferFunction(); |
296 | } |
297 | |
298 | void 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 | |
310 | void 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 | |
336 | void 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 | |
377 | void 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 | |
413 | QColorTransform 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 | |
427 | QColorTransform 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 | */ |
524 | QColorSpace::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 | */ |
549 | QColorSpace::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 | */ |
558 | QColorSpace::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 | */ |
572 | QColorSpace::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 | */ |
581 | QColorSpace::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 | */ |
600 | QColorSpace::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 | */ |
614 | QColorSpace::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 | |
626 | QColorSpace::~QColorSpace() = default; |
627 | |
628 | QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QColorSpacePrivate) |
629 | |
630 | QColorSpace::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 | */ |
642 | QColorSpace::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 | */ |
655 | QColorSpace::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 | */ |
669 | float 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 | */ |
681 | void 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 | */ |
705 | void 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 | */ |
727 | void 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 | */ |
754 | QColorSpace 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 | */ |
772 | QColorSpace 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 | */ |
789 | QColorSpace 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 | */ |
805 | void 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 | */ |
828 | void 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 | */ |
852 | void 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 | */ |
872 | QByteArray 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 | */ |
894 | QColorSpace 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 | */ |
907 | bool 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 | */ |
931 | bool 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 | */ |
981 | QColorTransform 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 | */ |
996 | QColorSpace::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 | */ |
1009 | QString 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 | */ |
1024 | void 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 | |
1043 | QDataStream &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 | |
1059 | QDataStream &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 |
1069 | QDebug 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 | |
1085 | QT_END_NAMESPACE |
1086 | |
1087 | #include "moc_qcolorspace.cpp" |
1088 | |