File indexing completed on 2024-11-10 04:56:38
0001 /* 0002 SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "colorspace.h" 0007 0008 #include <qassert.h> 0009 0010 namespace KWin 0011 { 0012 0013 static QMatrix4x4 matrixFromColumns(const QVector3D &first, const QVector3D &second, const QVector3D &third) 0014 { 0015 QMatrix4x4 ret; 0016 ret(0, 0) = first.x(); 0017 ret(1, 0) = first.y(); 0018 ret(2, 0) = first.z(); 0019 ret(0, 1) = second.x(); 0020 ret(1, 1) = second.y(); 0021 ret(2, 1) = second.z(); 0022 ret(0, 2) = third.x(); 0023 ret(1, 2) = third.y(); 0024 ret(2, 2) = third.z(); 0025 return ret; 0026 } 0027 0028 QVector3D Colorimetry::xyToXYZ(QVector2D xy) 0029 { 0030 return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y()); 0031 } 0032 0033 QVector2D Colorimetry::xyzToXY(QVector3D xyz) 0034 { 0035 xyz /= xyz.y(); 0036 return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), xyz.y() / (xyz.x() + xyz.y() + xyz.z())); 0037 } 0038 0039 QMatrix4x4 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint) 0040 { 0041 static const QMatrix4x4 bradford = []() { 0042 QMatrix4x4 ret; 0043 ret(0, 0) = 0.8951; 0044 ret(0, 1) = 0.2664; 0045 ret(0, 2) = -0.1614; 0046 ret(1, 0) = -0.7502; 0047 ret(1, 1) = 1.7135; 0048 ret(1, 2) = 0.0367; 0049 ret(2, 0) = 0.0389; 0050 ret(2, 1) = -0.0685; 0051 ret(2, 2) = 1.0296; 0052 return ret; 0053 }(); 0054 static const QMatrix4x4 inverseBradford = []() { 0055 QMatrix4x4 ret; 0056 ret(0, 0) = 0.9869929; 0057 ret(0, 1) = -0.1470543; 0058 ret(0, 2) = 0.1599627; 0059 ret(1, 0) = 0.4323053; 0060 ret(1, 1) = 0.5183603; 0061 ret(1, 2) = 0.0492912; 0062 ret(2, 0) = -0.0085287; 0063 ret(2, 1) = 0.0400428; 0064 ret(2, 2) = 0.9684867; 0065 return ret; 0066 }(); 0067 if (sourceWhitepoint == destinationWhitepoint) { 0068 return QMatrix4x4{}; 0069 } 0070 const QVector3D factors = (bradford * xyToXYZ(destinationWhitepoint)) / (bradford * xyToXYZ(sourceWhitepoint)); 0071 QMatrix4x4 adaptation{}; 0072 adaptation(0, 0) = factors.x(); 0073 adaptation(1, 1) = factors.y(); 0074 adaptation(2, 2) = factors.z(); 0075 return inverseBradford * adaptation * bradford; 0076 } 0077 0078 QMatrix4x4 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white) 0079 { 0080 const auto component_scale = (matrixFromColumns(red, green, blue)).inverted() * white; 0081 return matrixFromColumns(red * component_scale.x(), green * component_scale.y(), blue * component_scale.z()); 0082 } 0083 0084 Colorimetry Colorimetry::interpolateGamutTo(const Colorimetry &one, double factor) const 0085 { 0086 return Colorimetry{ 0087 m_red * (1 - factor) + one.red() * factor, 0088 m_green * (1 - factor) + one.green() * factor, 0089 m_blue * (1 - factor) + one.blue() * factor, 0090 m_white, // whitepoint should stay the same 0091 }; 0092 } 0093 0094 Colorimetry::Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white) 0095 : m_red(red) 0096 , m_green(green) 0097 , m_blue(blue) 0098 , m_white(white) 0099 , m_toXYZ(calculateToXYZMatrix(xyToXYZ(red), xyToXYZ(green), xyToXYZ(blue), xyToXYZ(white))) 0100 , m_fromXYZ(m_toXYZ.inverted()) 0101 { 0102 } 0103 0104 Colorimetry::Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white) 0105 : m_red(xyzToXY(red)) 0106 , m_green(xyzToXY(green)) 0107 , m_blue(xyzToXY(blue)) 0108 , m_white(xyzToXY(white)) 0109 , m_toXYZ(calculateToXYZMatrix(red, green, blue, white)) 0110 , m_fromXYZ(m_toXYZ.inverted()) 0111 { 0112 } 0113 0114 const QMatrix4x4 &Colorimetry::toXYZ() const 0115 { 0116 return m_toXYZ; 0117 } 0118 0119 const QMatrix4x4 &Colorimetry::fromXYZ() const 0120 { 0121 return m_fromXYZ; 0122 } 0123 0124 QMatrix4x4 Colorimetry::toOther(const Colorimetry &other) const 0125 { 0126 // rendering intent is relative colorimetric, so adapt to the different whitepoint 0127 return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ(); 0128 } 0129 0130 Colorimetry Colorimetry::adaptedTo(QVector2D newWhitepoint) const 0131 { 0132 const auto mat = chromaticAdaptationMatrix(this->white(), newWhitepoint); 0133 return Colorimetry{ 0134 xyzToXY(mat * xyToXYZ(red())), 0135 xyzToXY(mat * xyToXYZ(green())), 0136 xyzToXY(mat * xyToXYZ(blue())), 0137 newWhitepoint, 0138 }; 0139 } 0140 0141 bool Colorimetry::operator==(const Colorimetry &other) const 0142 { 0143 return red() == other.red() && green() == other.green() && blue() == other.blue() && white() == other.white(); 0144 } 0145 0146 bool Colorimetry::operator==(NamedColorimetry name) const 0147 { 0148 return *this == fromName(name); 0149 } 0150 0151 const QVector2D &Colorimetry::red() const 0152 { 0153 return m_red; 0154 } 0155 0156 const QVector2D &Colorimetry::green() const 0157 { 0158 return m_green; 0159 } 0160 0161 const QVector2D &Colorimetry::blue() const 0162 { 0163 return m_blue; 0164 } 0165 0166 const QVector2D &Colorimetry::white() const 0167 { 0168 return m_white; 0169 } 0170 0171 static const Colorimetry BT709 = Colorimetry{ 0172 QVector2D{0.64, 0.33}, 0173 QVector2D{0.30, 0.60}, 0174 QVector2D{0.15, 0.06}, 0175 QVector2D{0.3127, 0.3290}, 0176 }; 0177 0178 static const Colorimetry BT2020 = Colorimetry{ 0179 QVector2D{0.708, 0.292}, 0180 QVector2D{0.170, 0.797}, 0181 QVector2D{0.131, 0.046}, 0182 QVector2D{0.3127, 0.3290}, 0183 }; 0184 0185 const Colorimetry &Colorimetry::fromName(NamedColorimetry name) 0186 { 0187 switch (name) { 0188 case NamedColorimetry::BT709: 0189 return BT709; 0190 case NamedColorimetry::BT2020: 0191 return BT2020; 0192 } 0193 Q_UNREACHABLE(); 0194 } 0195 0196 const ColorDescription ColorDescription::sRGB = ColorDescription(NamedColorimetry::BT709, NamedTransferFunction::gamma22, 100, 0, 100, 100); 0197 0198 ColorDescription::ColorDescription(const Colorimetry &colorimety, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, const Colorimetry &sdrColorimetry) 0199 : m_colorimetry(colorimety) 0200 , m_transferFunction(tf) 0201 , m_sdrColorimetry(sdrColorimetry) 0202 , m_sdrBrightness(sdrBrightness) 0203 , m_minHdrBrightness(minHdrBrightness) 0204 , m_maxFrameAverageBrightness(maxFrameAverageBrightness) 0205 , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness) 0206 { 0207 } 0208 0209 ColorDescription::ColorDescription(NamedColorimetry colorimetry, NamedTransferFunction tf, double sdrBrightness, double minHdrBrightness, double maxFrameAverageBrightness, double maxHdrHighlightBrightness, const Colorimetry &sdrColorimetry) 0210 : m_colorimetry(Colorimetry::fromName(colorimetry)) 0211 , m_transferFunction(tf) 0212 , m_sdrColorimetry(sdrColorimetry) 0213 , m_sdrBrightness(sdrBrightness) 0214 , m_minHdrBrightness(minHdrBrightness) 0215 , m_maxFrameAverageBrightness(maxFrameAverageBrightness) 0216 , m_maxHdrHighlightBrightness(maxHdrHighlightBrightness) 0217 { 0218 } 0219 0220 const Colorimetry &ColorDescription::colorimetry() const 0221 { 0222 return m_colorimetry; 0223 } 0224 0225 const Colorimetry &ColorDescription::sdrColorimetry() const 0226 { 0227 return m_sdrColorimetry; 0228 } 0229 0230 NamedTransferFunction ColorDescription::transferFunction() const 0231 { 0232 return m_transferFunction; 0233 } 0234 0235 double ColorDescription::sdrBrightness() const 0236 { 0237 return m_sdrBrightness; 0238 } 0239 0240 double ColorDescription::minHdrBrightness() const 0241 { 0242 return m_minHdrBrightness; 0243 } 0244 0245 double ColorDescription::maxFrameAverageBrightness() const 0246 { 0247 return m_maxFrameAverageBrightness; 0248 } 0249 0250 double ColorDescription::maxHdrHighlightBrightness() const 0251 { 0252 return m_maxHdrHighlightBrightness; 0253 } 0254 0255 bool ColorDescription::operator==(const ColorDescription &other) const 0256 { 0257 return m_colorimetry == other.m_colorimetry 0258 && m_transferFunction == other.m_transferFunction 0259 && m_sdrGamutWideness == other.m_sdrGamutWideness 0260 && m_sdrBrightness == other.m_sdrBrightness 0261 && m_minHdrBrightness == other.m_minHdrBrightness 0262 && m_maxFrameAverageBrightness == other.m_maxFrameAverageBrightness 0263 && m_maxHdrHighlightBrightness == other.m_maxHdrHighlightBrightness; 0264 } 0265 0266 static float srgbToLinear(float sRGB) 0267 { 0268 if (sRGB < 0.04045) { 0269 return std::max(sRGB / 12.92, 0.0); 0270 } else { 0271 return std::clamp(std::pow((sRGB + 0.055) / 1.055, 12.0 / 5.0), 0.0, 1.0); 0272 } 0273 } 0274 0275 static float linearToSRGB(float linear) 0276 { 0277 if (linear < 0.0031308) { 0278 return std::max(linear / 12.92, 0.0); 0279 } else { 0280 return std::clamp(std::pow(linear, 5.0 / 12.0) * 1.055 - 0.055, 0.0, 1.0); 0281 } 0282 } 0283 0284 static float nitsToPQ(float nits) 0285 { 0286 const float normalized = std::clamp(nits / 10000.0f, 0.0f, 1.0f); 0287 const float c1 = 0.8359375; 0288 const float c2 = 18.8515625; 0289 const float c3 = 18.6875; 0290 const float m1 = 0.1593017578125; 0291 const float m2 = 78.84375; 0292 const float powed = std::pow(normalized, m1); 0293 const float num = c1 + c2 * powed; 0294 const float denum = 1 + c3 * powed; 0295 return std::pow(num / denum, m2); 0296 } 0297 0298 static float pqToNits(float pq) 0299 { 0300 const float c1 = 0.8359375; 0301 const float c2 = 18.8515625; 0302 const float c3 = 18.6875; 0303 const float m1_inv = 1.0 / 0.1593017578125; 0304 const float m2_inv = 1.0 / 78.84375; 0305 const float powed = std::pow(pq, m2_inv); 0306 const float num = std::max(powed - c1, 0.0f); 0307 const float den = c2 - c3 * powed; 0308 return 10000.0f * std::pow(num / den, m1_inv); 0309 } 0310 0311 static QVector3D clamp(const QVector3D &vect, float min = 0, float max = 1) 0312 { 0313 return QVector3D(std::clamp(vect.x(), min, max), std::clamp(vect.y(), min, max), std::clamp(vect.z(), min, max)); 0314 } 0315 0316 QVector3D ColorDescription::encodedToNits(const QVector3D &nits, NamedTransferFunction tf, double sdrBrightness) 0317 { 0318 switch (tf) { 0319 case NamedTransferFunction::sRGB: 0320 return sdrBrightness * QVector3D(srgbToLinear(nits.x()), srgbToLinear(nits.y()), srgbToLinear(nits.z())); 0321 case NamedTransferFunction::gamma22: 0322 return sdrBrightness * QVector3D(std::pow(nits.x(), 2.2), std::pow(nits.y(), 2.2), std::pow(nits.z(), 2.2)); 0323 case NamedTransferFunction::linear: 0324 return nits; 0325 case NamedTransferFunction::scRGB: 0326 return nits * 80.0f; 0327 case NamedTransferFunction::PerceptualQuantizer: 0328 return QVector3D(pqToNits(nits.x()), pqToNits(nits.y()), pqToNits(nits.z())); 0329 } 0330 Q_UNREACHABLE(); 0331 } 0332 0333 QVector3D ColorDescription::nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, double sdrBrightness) 0334 { 0335 switch (tf) { 0336 case NamedTransferFunction::sRGB: { 0337 const auto clamped = clamp(rgb / sdrBrightness); 0338 return QVector3D(linearToSRGB(clamped.x()), linearToSRGB(clamped.y()), linearToSRGB(clamped.z())); 0339 } 0340 case NamedTransferFunction::gamma22: { 0341 const auto clamped = clamp(rgb / sdrBrightness); 0342 return QVector3D(std::pow(clamped.x(), 1 / 2.2), std::pow(clamped.y(), 1 / 2.2), std::pow(clamped.z(), 1 / 2.2)); 0343 } 0344 case NamedTransferFunction::linear: 0345 return rgb; 0346 case NamedTransferFunction::scRGB: 0347 return rgb / 80.0f; 0348 case NamedTransferFunction::PerceptualQuantizer: 0349 return QVector3D(nitsToPQ(rgb.x()), nitsToPQ(rgb.y()), nitsToPQ(rgb.z())); 0350 } 0351 Q_UNREACHABLE(); 0352 } 0353 0354 QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const 0355 { 0356 rgb = encodedToNits(rgb, m_transferFunction, m_sdrBrightness); 0357 rgb = m_colorimetry.toOther(dst.colorimetry()) * rgb; 0358 return nitsToEncoded(rgb, dst.transferFunction(), dst.sdrBrightness()); 0359 } 0360 }