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