File indexing completed on 2024-05-12 15:59:35
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Wolthera van Hövell tot Westerflier 0003 * <griffinvalley@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #ifndef KOCOLORTRANSFERFUNCTIONS_H 0009 #define KOCOLORTRANSFERFUNCTIONS_H 0010 0011 #include <QVector> 0012 #include <QtGlobal> 0013 #include <cmath> 0014 0015 0016 #include <KoAlwaysInline.h> 0017 0018 /** 0019 * @brief The KoColorTransferFunctions class 0020 * 0021 * A number of often used transferFunctions. 0022 * 0023 * These functions can, at the time of writing, not be implemented 0024 * in ICC profiles, so instead, we apply or remove the curve as 0025 * necessary. 0026 */ 0027 0028 // Not all embedded nclx color space definitions can be converted to icc, so we 0029 // keep an enum to load those. 0030 enum class LinearizePolicy { 0031 KeepTheSame, 0032 LinearFromPQ, 0033 LinearFromHLG, 0034 LinearFromSMPTE428 0035 }; 0036 0037 static constexpr uint16_t max12bit = 4095.f; 0038 static constexpr float max16bit = 65535.0f; 0039 static constexpr float multiplier10bit = 1.0f / 1023.0f; 0040 static constexpr float multiplier12bit = 1.0f / 4095.0f; 0041 static constexpr float multiplier16bit = 1.0f / max16bit; 0042 0043 enum class ConversionPolicy { KeepTheSame, ApplyPQ, ApplyHLG, ApplySMPTE428 }; 0044 0045 ALWAYS_INLINE float applySmpte2048Curve(float x) noexcept { 0046 const float m1 = 2610.0f / 4096.0f / 4.0f; 0047 const float m2 = 2523.0f / 4096.0f * 128.0f; 0048 const float a1 = 3424.0f / 4096.0f; 0049 const float c2 = 2413.0f / 4096.0f * 32.0f; 0050 const float c3 = 2392.0f / 4096.0f * 32.0f; 0051 const float a4 = 1.0f; 0052 const float x_p = powf(0.008f * std::max(0.0f, x), m1); 0053 const float res = powf((a1 + c2 * x_p) / (a4 + c3 * x_p), m2); 0054 return res; 0055 } 0056 0057 ALWAYS_INLINE float removeSmpte2048Curve(float x) noexcept { 0058 const float m1_r = 4096.0f * 4.0f / 2610.0f; 0059 const float m2_r = 4096.0f / 2523.0f / 128.0f; 0060 const float a1 = 3424.0f / 4096.0f; 0061 const float c2 = 2413.0f / 4096.0f * 32.0f; 0062 const float c3 = 2392.0f / 4096.0f * 32.0f; 0063 0064 const float x_p = powf(x, m2_r); 0065 const float res = powf(qMax(0.0f, x_p - a1) / (c2 - c3 * x_p), m1_r); 0066 return res * 125.0f; 0067 } 0068 0069 // From ITU Bt. 2390-8 pg. 31, this calculates the gamma for the nominal peak. 0070 // This may differ per display regardless, but this is a good baseline. 0071 ALWAYS_INLINE float HLGOOTFGamma(float nominalPeak) noexcept { 0072 const float k = 1.111f; 0073 return 1.2f * powf(k, log2f(nominalPeak * (1.f / 1000.0f))); 0074 } 0075 0076 // The HLG OOTF needs to be applied to convert from 'display linear' to 'scene 0077 // linear'. Krita doesn't support sending tagged HLG to the display, so we have 0078 // to pretend we're always converting from PQ to HLG. 0079 ALWAYS_INLINE void applyHLGOOTF(QVector<float> &rgb, 0080 const QVector<double> &lumaCoefficients, 0081 float gamma = 1.2f, 0082 float nominalPeak = 1000.0f) noexcept { 0083 const float luma = (rgb[0] * lumaCoefficients[0]) + 0084 (rgb[1] * lumaCoefficients[1]) + 0085 (rgb[2] * lumaCoefficients[2]); 0086 const float a = nominalPeak * powf(luma, gamma - 1.f); 0087 rgb[0] *= a; 0088 rgb[1] *= a; 0089 rgb[2] *= a; 0090 } 0091 0092 // The HLG OOTF needs to be removed to convert from 'scene linear' to 'display 0093 // linear'. Krita doesn't support sending tagged HLG to the display, so we have 0094 // to pretend we're always converting from HLG to PQ. 0095 ALWAYS_INLINE void removeHLGOOTF(QVector<float> &rgb, 0096 const QVector<double> &lumaCoefficients, 0097 float gamma = 1.2f, 0098 float nominalPeak = 1000.0f) noexcept { 0099 const float luma = (rgb[0] * lumaCoefficients[0]) + 0100 (rgb[1] * lumaCoefficients[1]) + 0101 (rgb[2] * lumaCoefficients[2]); 0102 const float multiplier = 0103 powf(luma * (1.f / nominalPeak), (1.f - gamma) * (1.f / gamma)) * 0104 (1.f / nominalPeak); 0105 rgb[0] *= multiplier; 0106 rgb[1] *= multiplier; 0107 rgb[2] *= multiplier; 0108 } 0109 0110 ALWAYS_INLINE float applyHLGCurve(float x) noexcept { 0111 const float a = 0.17883277f; 0112 const float b = 0.28466892f; 0113 const float c = 0.55991073f; 0114 0115 if (x > 1.0f / 12.0f) { 0116 return (a * logf(12.0f * x - b) + c); 0117 } else { 0118 // return (sqrt(3.0) * powf(x, 0.5)); 0119 return (sqrtf(3.0f) * sqrtf(x)); 0120 } 0121 } 0122 0123 ALWAYS_INLINE float removeHLGCurve(float x) noexcept { 0124 const float a = 0.17883277f; 0125 const float b = 0.28466892f; 0126 const float c = 0.55991073f; 0127 if (x <= 0.5f) { 0128 // return (powf(x, 2.0) / 3.0); 0129 return x * x * (1.f / 3.0f); 0130 } else { 0131 return (expf((x - c) * (1.f / a)) + b) * (1.f / 12.0f); 0132 } 0133 } 0134 0135 ALWAYS_INLINE float applySMPTE_ST_428Curve(float x) noexcept { 0136 return powf(48.0f * x * (1.f / 52.37f), (1.f / 2.6f)); 0137 } 0138 0139 ALWAYS_INLINE float removeSMPTE_ST_428Curve(float x) noexcept { 0140 return (52.37f / 48.0f) * powf(x, 2.6f); 0141 } 0142 0143 #endif // KOCOLORTRANSFERFUNCTIONS_H