Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcolormatrix_p.h
Go to the documentation of this file.
1// Copyright (C) 2024 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#ifndef QCOLORMATRIX_H
5#define QCOLORMATRIX_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtGui/qtguiglobal.h>
19#include <QtCore/qpoint.h>
20#include <QtCore/private/qglobal_p.h>
21#include <QtCore/private/qsimd_p.h>
22#include <cmath>
23
25
26// An abstract 3 value color
28{
29public:
30 QColorVector() = default;
31 constexpr QColorVector(float x, float y, float z, float w = 0.0f) noexcept : x(x), y(y), z(z), w(w) { }
33 { return {float(chr.x() / chr.y()), 1.0f, float((1.0f - chr.x() - chr.y()) / chr.y())}; }
34 float x = 0.0f; // X, x, L, or red/cyan
35 float y = 0.0f; // Y, y, a, or green/magenta
36 float z = 0.0f; // Z, Y, b, or blue/yellow
37 float w = 0.0f; // unused, or black
38
39 constexpr bool isNull() const noexcept
40 {
41 return !x && !y && !z && !w;
42 }
43 bool isValid() const noexcept
44 {
45 return std::isfinite(x) && std::isfinite(y) && std::isfinite(z);
46 }
47
48 static constexpr bool isValidChromaticity(const QPointF &chr)
49 {
50 if (chr.x() < qreal(0.0) || chr.x() > qreal(1.0))
51 return false;
52 if (chr.y() <= qreal(0.0) || chr.y() > qreal(1.0))
53 return false;
54 if (chr.x() + chr.y() > qreal(1.0))
55 return false;
56 return true;
57 }
58
59 constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f, w * f); }
60 constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z, w + v.w); }
61 constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z, w - v.w); }
62 void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; w += v.w; }
63
65 {
66 if (isNull())
67 return QPointF();
68 float mag = 1.0f / (x + y + z);
69 return QPointF(x * mag, y * mag);
70 }
71
72 // Common whitepoints:
73 static constexpr QPointF D50Chromaticity() { return QPointF(0.34567, 0.35850); }
74 static constexpr QPointF D65Chromaticity() { return QPointF(0.31271, 0.32902); }
75 static constexpr QColorVector D50() { return fromXYChromaticity(D50Chromaticity()); }
76 static constexpr QColorVector D65() { return fromXYChromaticity(D65Chromaticity()); }
77
79 {
80 constexpr QColorVector ref = D50();
81 constexpr float eps = 0.008856f;
82 constexpr float kap = 903.3f;
83#if defined(__SSE2__)
84 const __m128 iref = _mm_setr_ps(1.f / ref.x, 1.f / ref.y, 1.f / ref.z, 0.f);
85 __m128 v = _mm_loadu_ps(&x);
86 v = _mm_mul_ps(v, iref);
87
88 const __m128 f3 = _mm_set1_ps(3.f);
89 __m128 est = _mm_add_ps(_mm_set1_ps(0.25f), _mm_mul_ps(v, _mm_set1_ps(0.75f))); // float est = 0.25f + (x * 0.75f);
90 __m128 estsq = _mm_mul_ps(est, est);
91 est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
92 _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
93 estsq = _mm_mul_ps(est, est);
94 est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
95 _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
96 estsq = _mm_mul_ps(est, est);
97 est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
98 _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
99 estsq = _mm_mul_ps(est, est);
100 est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
101 _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
102
103 __m128 kapmul = _mm_mul_ps(_mm_add_ps(_mm_mul_ps(v, _mm_set1_ps(kap)), _mm_set1_ps(16.f)),
104 _mm_set1_ps(1.f / 116.f)); // f_ = (kap * f_ + 16.f) * (1.f / 116.f);
105 __m128 cmpgt = _mm_cmpgt_ps(v, _mm_set1_ps(eps)); // if (f_ > eps)
106#if defined(__SSE4_1__)
107 v = _mm_blendv_ps(kapmul, est, cmpgt); // if (..) f_ =.. else f_ =..
108#else
109 v = _mm_or_ps(_mm_and_ps(cmpgt, est), _mm_andnot_ps(cmpgt, kapmul));
110#endif
111 alignas(16) float out[4];
112 _mm_store_ps(out, v);
113 const float L = 116.f * out[1] - 16.f;
114 const float a = 500.f * (out[0] - out[1]);
115 const float b = 200.f * (out[1] - out[2]);
116#else
117 float xr = x * (1.f / ref.x);
118 float yr = y * (1.f / ref.y);
119 float zr = z * (1.f / ref.z);
120
121 float fx, fy, fz;
122 if (xr > eps)
123 fx = fastCbrt(xr);
124 else
125 fx = (kap * xr + 16.f) * (1.f / 116.f);
126 if (yr > eps)
127 fy = fastCbrt(yr);
128 else
129 fy = (kap * yr + 16.f) * (1.f / 116.f);
130 if (zr > eps)
131 fz = fastCbrt(zr);
132 else
133 fz = (kap * zr + 16.f) * (1.f / 116.f);
134
135 const float L = 116.f * fy - 16.f;
136 const float a = 500.f * (fx - fy);
137 const float b = 200.f * (fy - fz);
138#endif
139 // We output Lab values that has been scaled to 0.0->1.0 values, see also labToXyz.
140 return QColorVector(L * (1.f / 100.f), (a + 128.f) * (1.f / 255.f), (b + 128.f) * (1.f / 255.f));
141 }
142
144 {
145 constexpr QColorVector ref = D50();
146 constexpr float eps = 0.008856f;
147 constexpr float kap = 903.3f;
148 // This transform has been guessed from the ICC spec, but it is not stated
149 // anywhere to be the one to use to map to and from 0.0->1.0 values:
150 const float L = x * 100.f;
151 const float a = (y * 255.f) - 128.f;
152 const float b = (z * 255.f) - 128.f;
153 // From here is official Lab->XYZ conversion:
154 float fy = (L + 16.f) * (1.f / 116.f);
155 float fx = fy + (a * (1.f / 500.f));
156 float fz = fy - (b * (1.f / 200.f));
157
158 float xr, yr, zr;
159 if (fx * fx * fx > eps)
160 xr = fx * fx * fx;
161 else
162 xr = (116.f * fx - 16) * (1.f / kap);
163 if (L > (kap * eps))
164 yr = fy * fy * fy;
165 else
166 yr = L * (1.f / kap);
167 if (fz * fz * fz > eps)
168 zr = fz * fz * fz;
169 else
170 zr = (116.f * fz - 16) * (1.f / kap);
171
172 xr = xr * ref.x;
173 yr = yr * ref.y;
174 zr = zr * ref.z;
175 return QColorVector(xr, yr, zr);
176 }
177 friend inline bool comparesEqual(const QColorVector &lhs, const QColorVector &rhs);
179
180private:
181 static float fastCbrt(float x)
182 {
183 // This gives us cube root within the precision we need.
184 float est = 0.25f + (x * 0.75f); // guessing a cube-root of numbers between 0.01 and 1.
185 est -= ((est * est * est) - x) / (3.f * (est * est));
186 est -= ((est * est * est) - x) / (3.f * (est * est));
187 est -= ((est * est * est) - x) / (3.f * (est * est));
188 est -= ((est * est * est) - x) / (3.f * (est * est));
189 // Q_ASSERT(qAbs(est - std::cbrt(x)) < 0.0001f);
190 return est;
191 }
192};
193
194inline bool comparesEqual(const QColorVector &v1, const QColorVector &v2)
195{
196 return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f))
197 && (std::abs(v1.y - v2.y) < (1.0f / 2048.0f))
198 && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f))
199 && (std::abs(v1.w - v2.w) < (1.0f / 2048.0f));
200}
201
202// A matrix mapping 3 value colors.
203// Not using QTransform because only floats are needed and performance is critical.
205{
206public:
207 // We are storing the matrix transposed as that is more convenient:
211
212 constexpr bool isNull() const
213 {
214 return r.isNull() && g.isNull() && b.isNull();
215 }
216 constexpr float determinant() const
217 {
218 return r.x * (b.z * g.y - g.z * b.y) -
219 r.y * (b.z * g.x - g.z * b.x) +
220 r.z * (b.y * g.x - g.y * b.x);
221 }
222 bool isValid() const
223 {
224 // A color matrix must be invertible
225 return std::isnormal(determinant());
226 }
227 bool isIdentity() const noexcept
228 {
229 return *this == identity();
230 }
231
233 {
234 float det = determinant();
235 det = 1.0f / det;
236 QColorMatrix inv;
237 inv.r.x = (g.y * b.z - b.y * g.z) * det;
238 inv.r.y = (b.y * r.z - r.y * b.z) * det;
239 inv.r.z = (r.y * g.z - g.y * r.z) * det;
240 inv.g.x = (b.x * g.z - g.x * b.z) * det;
241 inv.g.y = (r.x * b.z - b.x * r.z) * det;
242 inv.g.z = (g.x * r.z - r.x * g.z) * det;
243 inv.b.x = (g.x * b.y - b.x * g.y) * det;
244 inv.b.y = (b.x * r.y - r.x * b.y) * det;
245 inv.b.z = (r.x * g.y - g.x * r.y) * det;
246 return inv;
247 }
248 friend inline constexpr QColorMatrix operator*(const QColorMatrix &a, const QColorMatrix &o)
249 {
250 QColorMatrix comb;
251 comb.r.x = a.r.x * o.r.x + a.g.x * o.r.y + a.b.x * o.r.z;
252 comb.g.x = a.r.x * o.g.x + a.g.x * o.g.y + a.b.x * o.g.z;
253 comb.b.x = a.r.x * o.b.x + a.g.x * o.b.y + a.b.x * o.b.z;
254
255 comb.r.y = a.r.y * o.r.x + a.g.y * o.r.y + a.b.y * o.r.z;
256 comb.g.y = a.r.y * o.g.x + a.g.y * o.g.y + a.b.y * o.g.z;
257 comb.b.y = a.r.y * o.b.x + a.g.y * o.b.y + a.b.y * o.b.z;
258
259 comb.r.z = a.r.z * o.r.x + a.g.z * o.r.y + a.b.z * o.r.z;
260 comb.g.z = a.r.z * o.g.x + a.g.z * o.g.y + a.b.z * o.g.z;
261 comb.b.z = a.r.z * o.b.x + a.g.z * o.b.y + a.b.z * o.b.z;
262 return comb;
263
264 }
266 {
267 return QColorVector { c.x * r.x + c.y * g.x + c.z * b.x,
268 c.x * r.y + c.y * g.y + c.z * b.y,
269 c.x * r.z + c.y * g.z + c.z * b.z };
270 }
272 {
273 return QColorMatrix { { r.x, g.x, b.x },
274 { r.y, g.y, b.y },
275 { r.z, g.z, b.z } };
276 }
277
279 {
280 return { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } };
281 }
283 {
284 return QColorMatrix { { v.x, 0.0f, 0.0f },
285 { 0.0f, v.y, 0.0f },
286 { 0.0f, 0.0f, v.z } };
287 }
289 {
290 constexpr QColorVector whitePointD50 = QColorVector::D50();
291 if (whitePoint != whitePointD50) {
292 // A chromatic adaptation to map a white point to XYZ D50.
293
294 // The Bradford method chromatic adaptation matrix:
295 const QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
296 { 0.2664f, 1.7135f, -0.0685f },
297 { -0.1614f, 0.0367f, 1.0296f } };
298 const QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
299 { -0.1470543f, 0.5183603f, 0.0400428f },
300 { 0.1599627f, 0.0492912f, 0.9684867f } };
301
302 const QColorVector srcCone = abrad.map(whitePoint);
303 if (srcCone.x && srcCone.y && srcCone.z) {
304 const QColorVector dstCone = abrad.map(whitePointD50);
305 const QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
306 { 0, dstCone.y / srcCone.y, 0 },
307 { 0, 0, dstCone.z / srcCone.z } };
308 return abradinv * (wToD50 * abrad);
309 }
310 }
311 return QColorMatrix::identity();
312 }
313
314 // These are used to recognize matrices from ICC profiles:
316 {
317 return QColorMatrix { { 0.4360217452f, 0.2224751115f, 0.0139281144f },
318 { 0.3851087987f, 0.7169067264f, 0.0971015394f },
319 { 0.1430812478f, 0.0606181994f, 0.7141585946f } };
320 }
322 {
323 return QColorMatrix { { 0.6097189188f, 0.3111021519f, 0.0194766335f },
324 { 0.2052682191f, 0.6256770492f, 0.0608891509f },
325 { 0.1492247432f, 0.0632209629f, 0.7448224425f } };
326 }
328 {
329 return QColorMatrix { { 0.5150973201f, 0.2411795557f, -0.0010491034f },
330 { 0.2919696569f, 0.6922441125f, 0.0418830328f },
331 { 0.1571449190f, 0.0665764511f, 0.7843542695f } };
332 }
334 {
335 return QColorMatrix { { 0.7976672649f, 0.2880374491f, 0.0000000000f },
336 { 0.1351922452f, 0.7118769884f, 0.0000000000f },
337 { 0.0313525312f, 0.0000856627f, 0.8251883388f } };
338 }
339 friend inline bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs);
341};
342
343inline bool comparesEqual(const QColorMatrix &m1, const QColorMatrix &m2)
344{
345 return (m1.r == m2.r) && (m1.g == m2.g) && (m1.b == m2.b);
346}
347
349
350#endif // QCOLORMATRIX_P_H
QColorMatrix inverted() const
static QColorMatrix toXyzFromSRgb()
static QColorMatrix toXyzFromAdobeRgb()
bool isValid() const
friend bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs)
QColorVector g
constexpr float determinant() const
static QColorMatrix identity()
QColorMatrix transposed() const
static QColorMatrix fromScale(QColorVector v)
friend constexpr QColorMatrix operator*(const QColorMatrix &a, const QColorMatrix &o)
Q_DECLARE_EQUALITY_COMPARABLE(QColorMatrix)
QColorVector b
QColorVector r
bool isIdentity() const noexcept
static QColorMatrix toXyzFromDciP3D65()
static QColorMatrix chromaticAdaptation(const QColorVector &whitePoint)
static QColorMatrix toXyzFromProPhotoRgb()
QColorVector map(const QColorVector &c) const
constexpr bool isNull() const
static constexpr QColorVector D50()
static constexpr QPointF D65Chromaticity()
static constexpr bool isValidChromaticity(const QPointF &chr)
constexpr QColorVector(float x, float y, float z, float w=0.0f) noexcept
constexpr bool isNull() const noexcept
constexpr QColorVector operator-(const QColorVector &v) const
constexpr QColorVector operator*(float f) const
QColorVector labToXyz() const
static constexpr QColorVector fromXYChromaticity(QPointF chr)
QColorVector()=default
QPointF toChromaticity() const
static constexpr QPointF D50Chromaticity()
bool isValid() const noexcept
constexpr QColorVector operator+(const QColorVector &v) const
friend bool comparesEqual(const QColorVector &lhs, const QColorVector &rhs)
QColorVector xyzToLab() const
void operator+=(const QColorVector &v)
static constexpr QColorVector D65()
Q_DECLARE_EQUALITY_COMPARABLE(QColorVector)
\inmodule QtCore\reentrant
Definition qpoint.h:217
Combined button and popup list for selecting options.
bool comparesEqual(const QColorVector &v1, const QColorVector &v2)
GLint GLfloat GLfloat GLfloat v2
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLboolean r
[2]
GLfloat GLfloat f
GLint GLfloat GLfloat v1
GLboolean GLboolean g
GLint ref
GLint y
const GLubyte * c
double qreal
Definition qtypes.h:187
QTextStream out(stdout)
[7]