[PATCH] QColorTransferGeneric: fix the BT.2100 PQ EOTF
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>
Thu, 6 Feb 2025 20:00:55 +0000 (21:00 +0100)
committerPatrick Franz <deltaone@debian.org>
Fri, 18 Jul 2025 13:28:20 +0000 (15:28 +0200)
The PQ EOTF formula for BT.2100 [1][2] was incorrect. Fix it; while at
it, rename the variables to match the symbols used in the original
formula.

The inverse EOTF was correct, but also rename the variables there (for
the same reason).

[1] https://www.itu.int/rec/R-REC-BT.2100-2-201807-I/en
[2] https://en.wikipedia.org/wiki/Perceptual_quantizer#Technical_details

Change-Id: I6ce3a609824bee82053a16b3ff3cfc7cb396ce8f
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit a7ff4679facb9a44dff8b63a7e461ababa6aedfb)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 35c18eba64b6220e36ea14d32b911342e9d0921c)
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Gbp-Pq: Name upstream_QColorTransferGeneric_fix_BT2100PQEOTF.patch

src/gui/painting/qcolortransfergeneric_p.h

index c46f00e9dcf2fcc20fc6785435ac511363705df3..f9052509df5dbb9fef84fbef03510797bec31dca 100644 (file)
@@ -17,6 +17,7 @@
 
 #include <QtGui/private/qtguiglobal_p.h>
 
+#include <algorithm>
 #include <cmath>
 
 QT_BEGIN_NAMESPACE
@@ -82,18 +83,31 @@ private:
     constexpr static float m_hlg_b = 1.f - (4.f * m_hlg_a);
     constexpr static float m_hlg_c = 0.55991073f; // 0.5 - a * ln(4 * a)
 
+    // BT.2100-2 Reference PQ EOTF and inverse (see Table 4)
     // PQ to linear [0-1] -> [0-64]
-    static float pqToLinear(float x)
+    static float pqToLinear(float e)
     {
-        x = std::pow(x, 1.f / m_pq_m2);
-        return std::pow((m_pq_c1 - x) / (m_pq_c3 * x - m_pq_c2), (1.f / m_pq_m1)) * m_pq_f;
+        // m2-th root of E'
+        const float eRoot = std::pow(e, 1.f / m_pq_m2);
+        // rational transform
+        const float yBase = (std::max)(eRoot - m_pq_c1, 0.0f) / (m_pq_c2 - m_pq_c3 * eRoot);
+        // calculate Y = yBase^(1/m1)
+        const float y = std::pow(yBase, 1.f / m_pq_m1);
+        // scale Y to Fd
+        return y * m_pq_f;
     }
 
     // PQ from linear [0-64] -> [0-1]
-    static float pqFromLinear(float x)
+    static float pqFromLinear(float fd)
     {
-        x = std::pow(x * (1.f / m_pq_f), m_pq_m1);
-        return std::pow((m_pq_c1 + m_pq_c2 * x) / (1.f + m_pq_c3 * x), m_pq_m2);
+        // scale Fd to Y
+        const float y = fd * (1.f / m_pq_f);
+        // yRoot = Y^m1 -- "root" because m1 is <1
+        const float yRoot = std::pow(y, m_pq_m1);
+        // rational transform
+        const float eBase = (m_pq_c1 + m_pq_c2 * yRoot) / (1.f + m_pq_c3 * yRoot);
+        // calculate E' = eBase^m2
+        return std::pow(eBase, m_pq_m2);
     }
 
     constexpr static float m_pq_c1 =  107.f / 128.f; // c3 - c2 + 1