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