/src/libjxl/lib/jxl/cms/transfer_functions.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | // Transfer functions for color encodings. |
7 | | |
8 | | #ifndef LIB_JXL_CMS_TRANSFER_FUNCTIONS_H_ |
9 | | #define LIB_JXL_CMS_TRANSFER_FUNCTIONS_H_ |
10 | | |
11 | | #include <algorithm> |
12 | | #include <cmath> |
13 | | |
14 | | #include "lib/jxl/base/status.h" |
15 | | |
16 | | namespace jxl { |
17 | | |
18 | | // Definitions for BT.2100-2 transfer functions (used inside/outside SIMD): |
19 | | // "display" is linear light (nits) normalized to [0, 1]. |
20 | | // "encoded" is a nonlinear encoding (e.g. PQ) in [0, 1]. |
21 | | // "scene" is a linear function of photon counts, normalized to [0, 1]. |
22 | | |
23 | | // Despite the stated ranges, we need unbounded transfer functions: see |
24 | | // http://www.littlecms.com/CIC18_UnboundedCMM.pdf. Inputs can be negative or |
25 | | // above 1 due to chromatic adaptation. To avoid severe round-trip errors caused |
26 | | // by clamping, we mirror negative inputs via copysign (f(-x) = -f(x), see |
27 | | // https://developer.apple.com/documentation/coregraphics/cgcolorspace/1644735-extendedsrgb) |
28 | | // and extend the function domains above 1. |
29 | | |
30 | | // Hybrid Log-Gamma. |
31 | | class TF_HLG_Base { |
32 | | public: |
33 | | // EOTF. e = encoded. |
34 | 37.2k | static double DisplayFromEncoded(const double e) { return OOTF(InvOETF(e)); } |
35 | | |
36 | | // Inverse EOTF. d = display. |
37 | 0 | static double EncodedFromDisplay(const double d) { return OETF(InvOOTF(d)); } |
38 | | |
39 | | private: |
40 | | // OETF (defines the HLG approach). s = scene, returns encoded. |
41 | 0 | static double OETF(double s) { |
42 | 0 | if (s == 0.0) return 0.0; |
43 | 0 | const double original_sign = s; |
44 | 0 | s = std::abs(s); |
45 | |
|
46 | 0 | if (s <= kInv12) return copysignf(std::sqrt(3.0 * s), original_sign); |
47 | | |
48 | 0 | const double e = kA * std::log(12 * s - kB) + kC; |
49 | 0 | JXL_DASSERT(e > 0.0); |
50 | 0 | return copysignf(e, original_sign); |
51 | 0 | } |
52 | | |
53 | | // e = encoded, returns scene. |
54 | 37.2k | static double InvOETF(double e) { |
55 | 37.2k | if (e == 0.0) return 0.0; |
56 | 33.3k | const double original_sign = e; |
57 | 33.3k | e = std::abs(e); |
58 | | |
59 | 33.3k | if (e <= 0.5) return copysignf(e * e * (1.0 / 3), original_sign); |
60 | | |
61 | 16.7k | const double s = (std::exp((e - kC) * kRA) + kB) * kInv12; |
62 | 16.7k | JXL_DASSERT(s >= 0); |
63 | 16.7k | return copysignf(s, original_sign); |
64 | 33.3k | } |
65 | | |
66 | | // s = scene, returns display. |
67 | 37.2k | static double OOTF(const double s) { |
68 | | // The actual (red channel) OOTF is RD = alpha * YS^(gamma-1) * RS, where |
69 | | // YS = 0.2627 * RS + 0.6780 * GS + 0.0593 * BS. Let alpha = 1 so we return |
70 | | // "display" (normalized [0, 1]) instead of nits. Our transfer function |
71 | | // interface does not allow a dependency on YS. Fortunately, the system |
72 | | // gamma at 334 nits is 1.0, so this reduces to RD = RS. |
73 | 37.2k | return s; |
74 | 37.2k | } |
75 | | |
76 | | // d = display, returns scene. |
77 | 0 | static double InvOOTF(const double d) { |
78 | 0 | return d; // see OOTF(). |
79 | 0 | } |
80 | | |
81 | | protected: |
82 | | static constexpr double kA = 0.17883277; |
83 | | static constexpr double kRA = 1.0 / kA; |
84 | | static constexpr double kB = 1 - 4 * kA; |
85 | | static constexpr double kC = 0.5599107295; |
86 | | static constexpr double kInv12 = 1.0 / 12.0; |
87 | | }; |
88 | | |
89 | | // Perceptual Quantization |
90 | | class TF_PQ_Base { |
91 | | public: |
92 | 27.9k | static double DisplayFromEncoded(float display_intensity_target, double e) { |
93 | 27.9k | if (e == 0.0) return 0.0; |
94 | 25.6k | const double original_sign = e; |
95 | 25.6k | e = std::abs(e); |
96 | | |
97 | 25.6k | const double xp = std::pow(e, 1.0 / kM2); |
98 | 25.6k | const double num = std::max(xp - kC1, 0.0); |
99 | 25.6k | const double den = kC2 - kC3 * xp; |
100 | 25.6k | JXL_DASSERT(den != 0.0); |
101 | 25.6k | const double d = std::pow(num / den, 1.0 / kM1); |
102 | 25.6k | JXL_DASSERT(d >= 0.0); // Equal for e ~= 1E-9 |
103 | 25.6k | return copysignf(d * (10000.0f / display_intensity_target), original_sign); |
104 | 27.9k | } |
105 | | |
106 | | // Inverse EOTF. d = display. |
107 | 33.0k | static double EncodedFromDisplay(float display_intensity_target, double d) { |
108 | 33.0k | if (d == 0.0) return 0.0; |
109 | 19.7k | const double original_sign = d; |
110 | 19.7k | d = std::abs(d); |
111 | | |
112 | 19.7k | const double xp = |
113 | 19.7k | std::pow(d * (display_intensity_target * (1.0f / 10000.0f)), kM1); |
114 | 19.7k | const double num = kC1 + xp * kC2; |
115 | 19.7k | const double den = 1.0 + xp * kC3; |
116 | 19.7k | const double e = std::pow(num / den, kM2); |
117 | 19.7k | JXL_DASSERT(e > 0.0); |
118 | 19.7k | return copysignf(e, original_sign); |
119 | 33.0k | } |
120 | | |
121 | | protected: |
122 | | static constexpr double kM1 = 2610.0 / 16384; |
123 | | static constexpr double kM2 = (2523.0 / 4096) * 128; |
124 | | static constexpr double kC1 = 3424.0 / 4096; |
125 | | static constexpr double kC2 = (2413.0 / 4096) * 32; |
126 | | static constexpr double kC3 = (2392.0 / 4096) * 32; |
127 | | }; |
128 | | |
129 | | } // namespace jxl |
130 | | |
131 | | #endif // LIB_JXL_CMS_TRANSFER_FUNCTIONS_H_ |