Coverage Report

Created: 2025-06-16 07:00

/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_