Coverage Report

Created: 2025-06-16 07:00

/src/libjxl/lib/jxl/cms/tone_mapping.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
#ifndef LIB_JXL_CMS_TONE_MAPPING_H_
7
#define LIB_JXL_CMS_TONE_MAPPING_H_
8
9
#include <algorithm>
10
#include <array>
11
#include <cmath>
12
#include <cstddef>
13
14
#include "lib/jxl/base/common.h"
15
#include "lib/jxl/base/compiler_specific.h"
16
#include "lib/jxl/base/matrix_ops.h"
17
#include "lib/jxl/cms/transfer_functions.h"
18
19
namespace jxl {
20
21
using Range = std::array<float, 2>;
22
23
class Rec2408ToneMapperBase {
24
 public:
25
  explicit Rec2408ToneMapperBase(const Range& source_range,
26
                                 const Range& target_range,
27
                                 const Vector3& primaries_luminances)
28
6.62k
      : source_range_(source_range),
29
6.62k
        target_range_(target_range),
30
6.62k
        red_Y_(primaries_luminances[0]),
31
6.62k
        green_Y_(primaries_luminances[1]),
32
6.62k
        blue_Y_(primaries_luminances[2]) {}
33
34
  // TODO(eustas): test me
35
6.56k
  void ToneMap(Color& rgb) const {
36
6.56k
    const float luminance =
37
6.56k
        source_range_[1] *
38
6.56k
        (red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2]);
39
6.56k
    const float normalized_pq =
40
6.56k
        std::min(1.f, (InvEOTF(luminance) - pq_mastering_min_) *
41
6.56k
                          inv_pq_mastering_range_);
42
6.56k
    const float e2 = (normalized_pq < ks_) ? normalized_pq : P(normalized_pq);
43
6.56k
    const float one_minus_e2 = 1 - e2;
44
6.56k
    const float one_minus_e2_2 = one_minus_e2 * one_minus_e2;
45
6.56k
    const float one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
46
6.56k
    const float e3 = min_lum_ * one_minus_e2_4 + e2;
47
6.56k
    const float e4 = e3 * pq_mastering_range_ + pq_mastering_min_;
48
6.56k
    const float d4 =
49
6.56k
        TF_PQ_Base::DisplayFromEncoded(/*display_intensity_target=*/1.0, e4);
50
6.56k
    const float new_luminance = Clamp1(d4, 0.f, target_range_[1]);
51
6.56k
    const float min_luminance = 1e-6f;
52
6.56k
    const bool use_cap = (luminance <= min_luminance);
53
6.56k
    const float ratio = new_luminance / std::max(luminance, min_luminance);
54
6.56k
    const float cap = new_luminance * inv_target_peak_;
55
6.56k
    const float multiplier = ratio * normalizer_;
56
19.6k
    for (size_t idx : {0, 1, 2}) {
57
19.6k
      rgb[idx] = use_cap ? cap : rgb[idx] * multiplier;
58
19.6k
    }
59
6.56k
  }
60
61
 protected:
62
33.0k
  static float InvEOTF(const float luminance) {
63
33.0k
    return TF_PQ_Base::EncodedFromDisplay(/*display_intensity_target=*/1.0,
64
33.0k
                                          luminance);
65
33.0k
  }
66
5.59k
  float T(const float a) const { return (a - ks_) * inv_one_minus_ks_; }
67
5.59k
  float P(const float b) const {
68
5.59k
    const float t_b = T(b);
69
5.59k
    const float t_b_2 = t_b * t_b;
70
5.59k
    const float t_b_3 = t_b_2 * t_b;
71
5.59k
    return (2 * t_b_3 - 3 * t_b_2 + 1) * ks_ +
72
5.59k
           (t_b_3 - 2 * t_b_2 + t_b) * (1 - ks_) +
73
5.59k
           (-2 * t_b_3 + 3 * t_b_2) * max_lum_;
74
5.59k
  }
75
76
  const Range source_range_;
77
  const Range target_range_;
78
  const float red_Y_;
79
  const float green_Y_;
80
  const float blue_Y_;
81
82
  const float pq_mastering_min_ = InvEOTF(source_range_[0]);
83
  const float pq_mastering_max_ = InvEOTF(source_range_[1]);
84
  const float pq_mastering_range_ = pq_mastering_max_ - pq_mastering_min_;
85
  const float inv_pq_mastering_range_ = 1.0f / pq_mastering_range_;
86
  // TODO(eustas): divide instead of inverse-multiply?
87
  const float min_lum_ =
88
      (InvEOTF(target_range_[0]) - pq_mastering_min_) * inv_pq_mastering_range_;
89
  // TODO(eustas): divide instead of inverse-multiply?
90
  const float max_lum_ =
91
      (InvEOTF(target_range_[1]) - pq_mastering_min_) * inv_pq_mastering_range_;
92
  const float ks_ = 1.5f * max_lum_ - 0.5f;
93
94
  const float inv_one_minus_ks_ = 1.0f / std::max(1e-6f, 1.0f - ks_);
95
96
  const float normalizer_ = source_range_[1] / target_range_[1];
97
  const float inv_target_peak_ = 1.f / target_range_[1];
98
};
99
100
class HlgOOTF_Base {
101
 public:
102
  explicit HlgOOTF_Base(float source_luminance, float target_luminance,
103
                        const Vector3& primaries_luminances)
104
11.6k
      : HlgOOTF_Base(/*gamma=*/std::pow(1.111f, std::log2(target_luminance /
105
11.6k
                                                          source_luminance)),
106
11.6k
                     primaries_luminances) {}
107
108
  // TODO(eustas): test me
109
11.6k
  void Apply(Color& rgb) const {
110
11.6k
    if (!apply_ootf_) return;
111
11.6k
    const float luminance =
112
11.6k
        red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2];
113
11.6k
    const float ratio = std::min<float>(powf(luminance, exponent_), 1e9);
114
11.6k
    rgb[0] *= ratio;
115
11.6k
    rgb[1] *= ratio;
116
11.6k
    rgb[2] *= ratio;
117
11.6k
  }
118
119
 protected:
120
  explicit HlgOOTF_Base(float gamma, const Vector3& luminances)
121
11.7k
      : exponent_(gamma - 1),
122
11.7k
        red_Y_(luminances[0]),
123
11.7k
        green_Y_(luminances[1]),
124
11.7k
        blue_Y_(luminances[2]) {}
125
  const float exponent_;
126
  const bool apply_ootf_ = exponent_ < -0.01f || 0.01f < exponent_;
127
  const float red_Y_;
128
  const float green_Y_;
129
  const float blue_Y_;
130
};
131
132
static JXL_MAYBE_UNUSED void GamutMapScalar(Color& rgb,
133
                                            const Vector3& primaries_luminances,
134
18.2k
                                            float preserve_saturation = 0.1f) {
135
18.2k
  const float luminance = primaries_luminances[0] * rgb[0] +
136
18.2k
                          primaries_luminances[1] * rgb[1] +
137
18.2k
                          primaries_luminances[2] * rgb[2];
138
139
  // Desaturate out-of-gamut pixels. This is done by mixing each pixel
140
  // with just enough gray of the target luminance to make all
141
  // components non-negative.
142
  // - For saturation preservation, if a component is still larger than
143
  // 1 then the pixel is normalized to have a maximum component of 1.
144
  // That will reduce its luminance.
145
  // - For luminance preservation, getting all components below 1 is
146
  // done by mixing in yet more gray. That will desaturate it further.
147
18.2k
  float gray_mix_saturation = 0.0f;
148
18.2k
  float gray_mix_luminance = 0.0f;
149
54.6k
  for (size_t idx : {0, 1, 2}) {
150
54.6k
    const float& val = rgb[idx];
151
54.6k
    const float val_minus_gray = val - luminance;
152
54.6k
    const float inv_val_minus_gray =
153
54.6k
        1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray);
154
54.6k
    const float val_over_val_minus_gray = val * inv_val_minus_gray;
155
54.6k
    gray_mix_saturation =
156
54.6k
        (val_minus_gray >= 0.0f)
157
54.6k
            ? gray_mix_saturation
158
54.6k
            : std::max(gray_mix_saturation, val_over_val_minus_gray);
159
54.6k
    gray_mix_luminance =
160
54.6k
        std::max(gray_mix_luminance,
161
54.6k
                 (val_minus_gray <= 0.0f)
162
54.6k
                     ? gray_mix_saturation
163
54.6k
                     : (val_over_val_minus_gray - inv_val_minus_gray));
164
54.6k
  }
165
18.2k
  const float gray_mix =
166
18.2k
      Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) +
167
18.2k
              gray_mix_luminance),
168
18.2k
             0.0f, 1.0f);
169
54.6k
  for (size_t idx : {0, 1, 2}) {
170
54.6k
    float& val = rgb[idx];
171
54.6k
    val = gray_mix * (luminance - val) + val;
172
54.6k
  }
173
18.2k
  const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]});
174
18.2k
  const float normalizer = 1.0f / max_clr;
175
54.6k
  for (size_t idx : {0, 1, 2}) {
176
54.6k
    rgb[idx] *= normalizer;
177
54.6k
  }
178
18.2k
}
encode.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Line
Count
Source
134
18.2k
                                            float preserve_saturation = 0.1f) {
135
18.2k
  const float luminance = primaries_luminances[0] * rgb[0] +
136
18.2k
                          primaries_luminances[1] * rgb[1] +
137
18.2k
                          primaries_luminances[2] * rgb[2];
138
139
  // Desaturate out-of-gamut pixels. This is done by mixing each pixel
140
  // with just enough gray of the target luminance to make all
141
  // components non-negative.
142
  // - For saturation preservation, if a component is still larger than
143
  // 1 then the pixel is normalized to have a maximum component of 1.
144
  // That will reduce its luminance.
145
  // - For luminance preservation, getting all components below 1 is
146
  // done by mixing in yet more gray. That will desaturate it further.
147
18.2k
  float gray_mix_saturation = 0.0f;
148
18.2k
  float gray_mix_luminance = 0.0f;
149
54.6k
  for (size_t idx : {0, 1, 2}) {
150
54.6k
    const float& val = rgb[idx];
151
54.6k
    const float val_minus_gray = val - luminance;
152
54.6k
    const float inv_val_minus_gray =
153
54.6k
        1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray);
154
54.6k
    const float val_over_val_minus_gray = val * inv_val_minus_gray;
155
54.6k
    gray_mix_saturation =
156
54.6k
        (val_minus_gray >= 0.0f)
157
54.6k
            ? gray_mix_saturation
158
54.6k
            : std::max(gray_mix_saturation, val_over_val_minus_gray);
159
54.6k
    gray_mix_luminance =
160
54.6k
        std::max(gray_mix_luminance,
161
54.6k
                 (val_minus_gray <= 0.0f)
162
54.6k
                     ? gray_mix_saturation
163
54.6k
                     : (val_over_val_minus_gray - inv_val_minus_gray));
164
54.6k
  }
165
18.2k
  const float gray_mix =
166
18.2k
      Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) +
167
18.2k
              gray_mix_luminance),
168
18.2k
             0.0f, 1.0f);
169
54.6k
  for (size_t idx : {0, 1, 2}) {
170
54.6k
    float& val = rgb[idx];
171
54.6k
    val = gray_mix * (luminance - val) + val;
172
54.6k
  }
173
18.2k
  const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]});
174
18.2k
  const float normalizer = 1.0f / max_clr;
175
54.6k
  for (size_t idx : {0, 1, 2}) {
176
54.6k
    rgb[idx] *= normalizer;
177
54.6k
  }
178
18.2k
}
Unexecuted instantiation: enc_fields.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_ans.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_lz77.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_fast_lossless.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_frame.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_modular.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_patch_dictionary.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_dot_dictionary.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_detect_dots.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_debug_image.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_quant_weights.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_xyb.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_image_bundle.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_external_image.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_heuristics.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_adaptive_quantization.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_butteraugli_comparator.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_cache.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_group.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_progressive_split.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_chroma_from_luma.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_ac_strategy.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_entropy_coder.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: jxl_cms.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_jpeg_data.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: enc_encoding.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: color_encoding_internal.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: compressed_dc.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_cache.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: blending.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_external_image.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_frame.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_group.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_modular.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_noise.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_patch_dictionary.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: dec_xyb.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: decode.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: epf.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: frame_header.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: image_bundle.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: image_metadata.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: luminance.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: passes_state.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: quant_weights.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: render_pipeline.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: low_memory_render_pipeline.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: simple_render_pipeline.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_blending.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: render_pipeline_stage.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_chroma_upsampling.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_cms.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_epf.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_from_linear.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_gaborish.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_noise.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_patches.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_splines.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_spot.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_to_linear.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_tone_mapping.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_upsampling.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_write.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_xyb.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: stage_ycbcr.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
Unexecuted instantiation: decode_to_jpeg.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float)
179
180
}  // namespace jxl
181
182
#endif  // LIB_JXL_CMS_TONE_MAPPING_H_