KWin
Loading...
Searching...
No Matches
colorspace.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6#include "colorspace.h"
7
8#include <qassert.h>
9
10namespace KWin
11{
12
13static QMatrix4x4 matrixFromColumns(const QVector3D &first, const QVector3D &second, const QVector3D &third)
14{
15 QMatrix4x4 ret;
16 ret(0, 0) = first.x();
17 ret(1, 0) = first.y();
18 ret(2, 0) = first.z();
19 ret(0, 1) = second.x();
20 ret(1, 1) = second.y();
21 ret(2, 1) = second.z();
22 ret(0, 2) = third.x();
23 ret(1, 2) = third.y();
24 ret(2, 2) = third.z();
25 return ret;
26}
27
28QVector3D Colorimetry::xyToXYZ(QVector2D xy)
29{
30 return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y());
31}
32
33QVector2D Colorimetry::xyzToXY(QVector3D xyz)
34{
35 xyz /= xyz.y();
36 return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), xyz.y() / (xyz.x() + xyz.y() + xyz.z()));
37}
38
39QMatrix4x4 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint)
40{
41 static const QMatrix4x4 bradford = []() {
42 QMatrix4x4 ret;
43 ret(0, 0) = 0.8951;
44 ret(0, 1) = 0.2664;
45 ret(0, 2) = -0.1614;
46 ret(1, 0) = -0.7502;
47 ret(1, 1) = 1.7135;
48 ret(1, 2) = 0.0367;
49 ret(2, 0) = 0.0389;
50 ret(2, 1) = -0.0685;
51 ret(2, 2) = 1.0296;
52 return ret;
53 }();
54 static const QMatrix4x4 inverseBradford = []() {
55 QMatrix4x4 ret;
56 ret(0, 0) = 0.9869929;
57 ret(0, 1) = -0.1470543;
58 ret(0, 2) = 0.1599627;
59 ret(1, 0) = 0.4323053;
60 ret(1, 1) = 0.5183603;
61 ret(1, 2) = 0.0492912;
62 ret(2, 0) = -0.0085287;
63 ret(2, 1) = 0.0400428;
64 ret(2, 2) = 0.9684867;
65 return ret;
66 }();
67 if (sourceWhitepoint == destinationWhitepoint) {
68 return QMatrix4x4{};
69 }
70 const QVector3D factors = (bradford * xyToXYZ(destinationWhitepoint)) / (bradford * xyToXYZ(sourceWhitepoint));
71 QMatrix4x4 adaptation{};
72 adaptation(0, 0) = factors.x();
73 adaptation(1, 1) = factors.y();
74 adaptation(2, 2) = factors.z();
75 return inverseBradford * adaptation * bradford;
76}
77
78QMatrix4x4 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
79{
80 const auto component_scale = (matrixFromColumns(red, green, blue)).inverted() * white;
81 return matrixFromColumns(red * component_scale.x(), green * component_scale.y(), blue * component_scale.z());
82}
83
85{
86 return Colorimetry{
87 m_red * (1 - factor) + one.red() * factor,
88 m_green * (1 - factor) + one.green() * factor,
89 m_blue * (1 - factor) + one.blue() * factor,
90 m_white, // whitepoint should stay the same
91 };
92}
93
94Colorimetry::Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white)
95 : m_red(red)
96 , m_green(green)
97 , m_blue(blue)
98 , m_white(white)
99 , m_toXYZ(calculateToXYZMatrix(xyToXYZ(red), xyToXYZ(green), xyToXYZ(blue), xyToXYZ(white)))
100 , m_fromXYZ(m_toXYZ.inverted())
101{
102}
103
104Colorimetry::Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
105 : m_red(xyzToXY(red))
106 , m_green(xyzToXY(green))
107 , m_blue(xyzToXY(blue))
108 , m_white(xyzToXY(white))
109 , m_toXYZ(calculateToXYZMatrix(red, green, blue, white))
110 , m_fromXYZ(m_toXYZ.inverted())
111{
112}
113
114const QMatrix4x4 &Colorimetry::toXYZ() const
115{
116 return m_toXYZ;
117}
118
119const QMatrix4x4 &Colorimetry::fromXYZ() const
120{
121 return m_fromXYZ;
122}
123
124QMatrix4x4 Colorimetry::toOther(const Colorimetry &other) const
125{
126 // rendering intent is relative colorimetric, so adapt to the different whitepoint
127 return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ();
128}
129
130Colorimetry Colorimetry::adaptedTo(QVector2D newWhitepoint) const
131{
132 const auto mat = chromaticAdaptationMatrix(this->white(), newWhitepoint);
133 return Colorimetry{
134 xyzToXY(mat * xyToXYZ(red())),
135 xyzToXY(mat * xyToXYZ(green())),
136 xyzToXY(mat * xyToXYZ(blue())),
137 newWhitepoint,
138 };
139}
140
141bool Colorimetry::operator==(const Colorimetry &other) const
142{
143 return red() == other.red() && green() == other.green() && blue() == other.blue() && white() == other.white();
144}
145
147{
148 return *this == fromName(name);
149}
150
151const QVector2D &Colorimetry::red() const
152{
153 return m_red;
154}
155
156const QVector2D &Colorimetry::green() const
157{
158 return m_green;
159}
160
161const QVector2D &Colorimetry::blue() const
162{
163 return m_blue;
164}
165
166const QVector2D &Colorimetry::white() const
167{
168 return m_white;
169}
170
171static const Colorimetry BT709 = Colorimetry{
172 QVector2D{0.64, 0.33},
173 QVector2D{0.30, 0.60},
174 QVector2D{0.15, 0.06},
175 QVector2D{0.3127, 0.3290},
176};
177
178static const Colorimetry BT2020 = Colorimetry{
179 QVector2D{0.708, 0.292},
180 QVector2D{0.170, 0.797},
181 QVector2D{0.131, 0.046},
182 QVector2D{0.3127, 0.3290},
183};
184
186{
187 switch (name) {
189 return BT709;
191 return BT2020;
192 }
193 Q_UNREACHABLE();
194}
195
197
198ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, const Colorimetry &sdrColorimetry)
199 : m_colorimetry(colorimety)
200 , m_transferFunction(tf)
201 , m_sdrColorimetry(sdrColorimetry)
202 , m_sdrBrightness(sdrBrightness)
203 , m_minHdrBrightness(minHdrBrightness)
204 , m_maxFrameAverageBrightness(maxFrameAverageBrightness)
205 , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness)
206{
207}
208
209ColorDescription::ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, const Colorimetry &sdrColorimetry)
210 : m_colorimetry(Colorimetry::fromName(colorimetry))
211 , m_transferFunction(tf)
212 , m_sdrColorimetry(sdrColorimetry)
213 , m_sdrBrightness(sdrBrightness)
214 , m_minHdrBrightness(minHdrBrightness)
215 , m_maxFrameAverageBrightness(maxFrameAverageBrightness)
216 , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness)
217{
218}
219
221{
222 return m_colorimetry;
223}
224
226{
227 return m_sdrColorimetry;
228}
229
231{
232 return m_transferFunction;
233}
234
236{
237 return m_sdrBrightness;
238}
239
241{
242 return m_minHdrBrightness;
243}
244
246{
247 return m_maxFrameAverageBrightness;
248}
249
251{
252 return m_maxHdrHighlightBrightness;
253}
254
256{
257 return m_colorimetry == other.m_colorimetry
258 && m_transferFunction == other.m_transferFunction
259 && m_sdrGamutWideness == other.m_sdrGamutWideness
260 && m_sdrBrightness == other.m_sdrBrightness
261 && m_minHdrBrightness == other.m_minHdrBrightness
262 && m_maxFrameAverageBrightness == other.m_maxFrameAverageBrightness
263 && m_maxHdrHighlightBrightness == other.m_maxHdrHighlightBrightness;
264}
265
266static float srgbToLinear(float sRGB)
267{
268 if (sRGB < 0.04045) {
269 return std::max(sRGB / 12.92, 0.0);
270 } else {
271 return std::clamp(std::pow((sRGB + 0.055) / 1.055, 12.0 / 5.0), 0.0, 1.0);
272 }
273}
274
275static float linearToSRGB(float linear)
276{
277 if (linear < 0.0031308) {
278 return std::max(linear / 12.92, 0.0);
279 } else {
280 return std::clamp(std::pow(linear, 5.0 / 12.0) * 1.055 - 0.055, 0.0, 1.0);
281 }
282}
283
284static float nitsToPQ(float nits)
285{
286 const float normalized = std::clamp(nits / 10000.0f, 0.0f, 1.0f);
287 const float c1 = 0.8359375;
288 const float c2 = 18.8515625;
289 const float c3 = 18.6875;
290 const float m1 = 0.1593017578125;
291 const float m2 = 78.84375;
292 const float powed = std::pow(normalized, m1);
293 const float num = c1 + c2 * powed;
294 const float denum = 1 + c3 * powed;
295 return std::pow(num / denum, m2);
296}
297
298static float pqToNits(float pq)
299{
300 const float c1 = 0.8359375;
301 const float c2 = 18.8515625;
302 const float c3 = 18.6875;
303 const float m1_inv = 1.0 / 0.1593017578125;
304 const float m2_inv = 1.0 / 78.84375;
305 const float powed = std::pow(pq, m2_inv);
306 const float num = std::max(powed - c1, 0.0f);
307 const float den = c2 - c3 * powed;
308 return 10000.0f * std::pow(num / den, m1_inv);
309}
310
311static QVector3D clamp(const QVector3D &vect, float min = 0, float max = 1)
312{
313 return QVector3D(std::clamp(vect.x(), min, max), std::clamp(vect.y(), min, max), std::clamp(vect.z(), min, max));
314}
315
316QVector3D ColorDescription::encodedToNits(const QVector3D &nits, NamedTransferFunction tf, double sdrBrightness)
317{
318 switch (tf) {
320 return sdrBrightness * QVector3D(srgbToLinear(nits.x()), srgbToLinear(nits.y()), srgbToLinear(nits.z()));
322 return sdrBrightness * QVector3D(std::pow(nits.x(), 2.2), std::pow(nits.y(), 2.2), std::pow(nits.z(), 2.2));
324 return nits;
326 return nits * 80.0f;
328 return QVector3D(pqToNits(nits.x()), pqToNits(nits.y()), pqToNits(nits.z()));
329 }
330 Q_UNREACHABLE();
331}
332
333QVector3D ColorDescription::nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, double sdrBrightness)
334{
335 switch (tf) {
337 const auto clamped = clamp(rgb / sdrBrightness);
338 return QVector3D(linearToSRGB(clamped.x()), linearToSRGB(clamped.y()), linearToSRGB(clamped.z()));
339 }
341 const auto clamped = clamp(rgb / sdrBrightness);
342 return QVector3D(std::pow(clamped.x(), 1 / 2.2), std::pow(clamped.y(), 1 / 2.2), std::pow(clamped.z(), 1 / 2.2));
343 }
345 return rgb;
347 return rgb / 80.0f;
349 return QVector3D(nitsToPQ(rgb.x()), nitsToPQ(rgb.y()), nitsToPQ(rgb.z()));
350 }
351 Q_UNREACHABLE();
352}
353
354QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const
355{
356 rgb = encodedToNits(rgb, m_transferFunction, m_sdrBrightness);
357 rgb = m_colorimetry.toOther(dst.colorimetry()) * rgb;
358 return nitsToEncoded(rgb, dst.transferFunction(), dst.sdrBrightness());
359}
360}
NamedTransferFunction transferFunction() const
double minHdrBrightness() const
static const ColorDescription sRGB
Definition colorspace.h:132
bool operator==(const ColorDescription &other) const
double maxHdrHighlightBrightness() const
const Colorimetry & colorimetry() const
static QVector3D nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, double sdrBrightness)
double maxFrameAverageBrightness() const
const Colorimetry & sdrColorimetry() const
ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, const Colorimetry &sdrColorimetry=Colorimetry::fromName(NamedColorimetry::BT709))
static QVector3D encodedToNits(const QVector3D &nits, NamedTransferFunction tf, double sdrBrightness)
double sdrBrightness() const
QVector3D mapTo(QVector3D rgb, const ColorDescription &other) const
static QMatrix4x4 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
Colorimetry interpolateGamutTo(const Colorimetry &one, double factor) const
static QMatrix4x4 chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint)
const QVector2D & blue() const
Colorimetry adaptedTo(QVector2D newWhitepoint) const
static QVector3D xyToXYZ(QVector2D xy)
QMatrix4x4 toOther(const Colorimetry &colorimetry) const
Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white)
const QVector2D & red() const
const QMatrix4x4 & toXYZ() const
static QVector2D xyzToXY(QVector3D xyz)
static const Colorimetry & fromName(NamedColorimetry name)
const QVector2D & white() const
const QVector2D & green() const
bool operator==(const Colorimetry &other) const
const QMatrix4x4 & fromXYZ() const
NamedColorimetry
Definition colorspace.h:17
NamedTransferFunction
Definition colorspace.h:90