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