/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 <cmath> |
11 | | #include <cstddef> |
12 | | #include <utility> |
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 | | class Rec2408ToneMapperBase { |
22 | | public: |
23 | | explicit Rec2408ToneMapperBase(std::pair<float, float> source_range, |
24 | | std::pair<float, float> target_range, |
25 | | const Vector3& primaries_luminances) |
26 | 6.73M | : source_range_(std::move(source_range)), |
27 | 6.73M | target_range_(std::move(target_range)), |
28 | 6.73M | red_Y_(primaries_luminances[0]), |
29 | 6.73M | green_Y_(primaries_luminances[1]), |
30 | 6.73M | blue_Y_(primaries_luminances[2]) {} |
31 | | |
32 | | // TODO(eustas): test me |
33 | 6.73M | void ToneMap(Color& rgb) const { |
34 | 6.73M | const float luminance = |
35 | 6.73M | source_range_.second * |
36 | 6.73M | (red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2]); |
37 | 6.73M | const float normalized_pq = |
38 | 6.73M | std::min(1.f, (InvEOTF(luminance) - pq_mastering_min_) * |
39 | 6.73M | inv_pq_mastering_range_); |
40 | 6.73M | const float e2 = (normalized_pq < ks_) ? normalized_pq : P(normalized_pq); |
41 | 6.73M | const float one_minus_e2 = 1 - e2; |
42 | 6.73M | const float one_minus_e2_2 = one_minus_e2 * one_minus_e2; |
43 | 6.73M | const float one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2; |
44 | 6.73M | const float e3 = min_lum_ * one_minus_e2_4 + e2; |
45 | 6.73M | const float e4 = e3 * pq_mastering_range_ + pq_mastering_min_; |
46 | 6.73M | const float d4 = |
47 | 6.73M | TF_PQ_Base::DisplayFromEncoded(/*display_intensity_target=*/1.0, e4); |
48 | 6.73M | const float new_luminance = Clamp1(d4, 0.f, target_range_.second); |
49 | 6.73M | const float min_luminance = 1e-6f; |
50 | 6.73M | const bool use_cap = (luminance <= min_luminance); |
51 | 6.73M | const float ratio = new_luminance / std::max(luminance, min_luminance); |
52 | 6.73M | const float cap = new_luminance * inv_target_peak_; |
53 | 6.73M | const float multiplier = ratio * normalizer_; |
54 | 20.1M | for (size_t idx : {0, 1, 2}) { |
55 | 20.1M | rgb[idx] = use_cap ? cap : rgb[idx] * multiplier; |
56 | 20.1M | } |
57 | 6.73M | } |
58 | | |
59 | | protected: |
60 | 33.6M | static float InvEOTF(const float luminance) { |
61 | 33.6M | return TF_PQ_Base::EncodedFromDisplay(/*display_intensity_target=*/1.0, |
62 | 33.6M | luminance); |
63 | 33.6M | } |
64 | 5.74M | float T(const float a) const { return (a - ks_) * inv_one_minus_ks_; } |
65 | 5.74M | float P(const float b) const { |
66 | 5.74M | const float t_b = T(b); |
67 | 5.74M | const float t_b_2 = t_b * t_b; |
68 | 5.74M | const float t_b_3 = t_b_2 * t_b; |
69 | 5.74M | return (2 * t_b_3 - 3 * t_b_2 + 1) * ks_ + |
70 | 5.74M | (t_b_3 - 2 * t_b_2 + t_b) * (1 - ks_) + |
71 | 5.74M | (-2 * t_b_3 + 3 * t_b_2) * max_lum_; |
72 | 5.74M | } |
73 | | |
74 | | const std::pair<float, float> source_range_; |
75 | | const std::pair<float, float> target_range_; |
76 | | const float red_Y_; |
77 | | const float green_Y_; |
78 | | const float blue_Y_; |
79 | | |
80 | | const float pq_mastering_min_ = InvEOTF(source_range_.first); |
81 | | const float pq_mastering_max_ = InvEOTF(source_range_.second); |
82 | | const float pq_mastering_range_ = pq_mastering_max_ - pq_mastering_min_; |
83 | | const float inv_pq_mastering_range_ = 1.0f / pq_mastering_range_; |
84 | | // TODO(eustas): divide instead of inverse-multiply? |
85 | | const float min_lum_ = (InvEOTF(target_range_.first) - pq_mastering_min_) * |
86 | | inv_pq_mastering_range_; |
87 | | // TODO(eustas): divide instead of inverse-multiply? |
88 | | const float max_lum_ = (InvEOTF(target_range_.second) - pq_mastering_min_) * |
89 | | inv_pq_mastering_range_; |
90 | | const float ks_ = 1.5f * max_lum_ - 0.5f; |
91 | | |
92 | | const float inv_one_minus_ks_ = 1.0f / std::max(1e-6f, 1.0f - ks_); |
93 | | |
94 | | const float normalizer_ = source_range_.second / target_range_.second; |
95 | | const float inv_target_peak_ = 1.f / target_range_.second; |
96 | | }; |
97 | | |
98 | | class HlgOOTF_Base { |
99 | | public: |
100 | | explicit HlgOOTF_Base(float source_luminance, float target_luminance, |
101 | | const Vector3& primaries_luminances) |
102 | 4.55M | : HlgOOTF_Base(/*gamma=*/std::pow(1.111f, std::log2(target_luminance / |
103 | 4.55M | source_luminance)), |
104 | 4.55M | primaries_luminances) {} |
105 | | |
106 | | // TODO(eustas): test me |
107 | 4.55M | void Apply(Color& rgb) const { |
108 | 4.55M | if (!apply_ootf_) return; |
109 | 4.55M | const float luminance = |
110 | 4.55M | red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2]; |
111 | 4.55M | const float ratio = std::min<float>(powf(luminance, exponent_), 1e9); |
112 | 4.55M | rgb[0] *= ratio; |
113 | 4.55M | rgb[1] *= ratio; |
114 | 4.55M | rgb[2] *= ratio; |
115 | 4.55M | } |
116 | | |
117 | | protected: |
118 | | explicit HlgOOTF_Base(float gamma, const Vector3& luminances) |
119 | 4.55M | : exponent_(gamma - 1), |
120 | 4.55M | red_Y_(luminances[0]), |
121 | 4.55M | green_Y_(luminances[1]), |
122 | 4.55M | blue_Y_(luminances[2]) {} |
123 | | const float exponent_; |
124 | | const bool apply_ootf_ = exponent_ < -0.01f || 0.01f < exponent_; |
125 | | const float red_Y_; |
126 | | const float green_Y_; |
127 | | const float blue_Y_; |
128 | | }; |
129 | | |
130 | | static JXL_MAYBE_UNUSED void GamutMapScalar(Color& rgb, |
131 | | const Vector3& primaries_luminances, |
132 | 11.2M | float preserve_saturation = 0.1f) { |
133 | 11.2M | const float luminance = primaries_luminances[0] * rgb[0] + |
134 | 11.2M | primaries_luminances[1] * rgb[1] + |
135 | 11.2M | primaries_luminances[2] * rgb[2]; |
136 | | |
137 | | // Desaturate out-of-gamut pixels. This is done by mixing each pixel |
138 | | // with just enough gray of the target luminance to make all |
139 | | // components non-negative. |
140 | | // - For saturation preservation, if a component is still larger than |
141 | | // 1 then the pixel is normalized to have a maximum component of 1. |
142 | | // That will reduce its luminance. |
143 | | // - For luminance preservation, getting all components below 1 is |
144 | | // done by mixing in yet more gray. That will desaturate it further. |
145 | 11.2M | float gray_mix_saturation = 0.0f; |
146 | 11.2M | float gray_mix_luminance = 0.0f; |
147 | 33.8M | for (size_t idx : {0, 1, 2}) { |
148 | 33.8M | const float& val = rgb[idx]; |
149 | 33.8M | const float val_minus_gray = val - luminance; |
150 | 33.8M | const float inv_val_minus_gray = |
151 | 33.8M | 1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray); |
152 | 33.8M | const float val_over_val_minus_gray = val * inv_val_minus_gray; |
153 | 33.8M | gray_mix_saturation = |
154 | 33.8M | (val_minus_gray >= 0.0f) |
155 | 33.8M | ? gray_mix_saturation |
156 | 33.8M | : std::max(gray_mix_saturation, val_over_val_minus_gray); |
157 | 33.8M | gray_mix_luminance = |
158 | 33.8M | std::max(gray_mix_luminance, |
159 | 33.8M | (val_minus_gray <= 0.0f) |
160 | 33.8M | ? gray_mix_saturation |
161 | 33.8M | : (val_over_val_minus_gray - inv_val_minus_gray)); |
162 | 33.8M | } |
163 | 11.2M | const float gray_mix = |
164 | 11.2M | Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) + |
165 | 11.2M | gray_mix_luminance), |
166 | 11.2M | 0.0f, 1.0f); |
167 | 33.8M | for (size_t idx : {0, 1, 2}) { |
168 | 33.8M | float& val = rgb[idx]; |
169 | 33.8M | val = gray_mix * (luminance - val) + val; |
170 | 33.8M | } |
171 | 11.2M | const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]}); |
172 | 11.2M | const float normalizer = 1.0f / max_clr; |
173 | 33.8M | for (size_t idx : {0, 1, 2}) { |
174 | 33.8M | rgb[idx] *= normalizer; |
175 | 33.8M | } |
176 | 11.2M | } encode.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float) Line | Count | Source | 132 | 11.2M | float preserve_saturation = 0.1f) { | 133 | 11.2M | const float luminance = primaries_luminances[0] * rgb[0] + | 134 | 11.2M | primaries_luminances[1] * rgb[1] + | 135 | 11.2M | primaries_luminances[2] * rgb[2]; | 136 | | | 137 | | // Desaturate out-of-gamut pixels. This is done by mixing each pixel | 138 | | // with just enough gray of the target luminance to make all | 139 | | // components non-negative. | 140 | | // - For saturation preservation, if a component is still larger than | 141 | | // 1 then the pixel is normalized to have a maximum component of 1. | 142 | | // That will reduce its luminance. | 143 | | // - For luminance preservation, getting all components below 1 is | 144 | | // done by mixing in yet more gray. That will desaturate it further. | 145 | 11.2M | float gray_mix_saturation = 0.0f; | 146 | 11.2M | float gray_mix_luminance = 0.0f; | 147 | 33.8M | for (size_t idx : {0, 1, 2}) { | 148 | 33.8M | const float& val = rgb[idx]; | 149 | 33.8M | const float val_minus_gray = val - luminance; | 150 | 33.8M | const float inv_val_minus_gray = | 151 | 33.8M | 1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray); | 152 | 33.8M | const float val_over_val_minus_gray = val * inv_val_minus_gray; | 153 | 33.8M | gray_mix_saturation = | 154 | 33.8M | (val_minus_gray >= 0.0f) | 155 | 33.8M | ? gray_mix_saturation | 156 | 33.8M | : std::max(gray_mix_saturation, val_over_val_minus_gray); | 157 | 33.8M | gray_mix_luminance = | 158 | 33.8M | std::max(gray_mix_luminance, | 159 | 33.8M | (val_minus_gray <= 0.0f) | 160 | 33.8M | ? gray_mix_saturation | 161 | 33.8M | : (val_over_val_minus_gray - inv_val_minus_gray)); | 162 | 33.8M | } | 163 | 11.2M | const float gray_mix = | 164 | 11.2M | Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) + | 165 | 11.2M | gray_mix_luminance), | 166 | 11.2M | 0.0f, 1.0f); | 167 | 33.8M | for (size_t idx : {0, 1, 2}) { | 168 | 33.8M | float& val = rgb[idx]; | 169 | 33.8M | val = gray_mix * (luminance - val) + val; | 170 | 33.8M | } | 171 | 11.2M | const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]}); | 172 | 11.2M | const float normalizer = 1.0f / max_clr; | 173 | 33.8M | for (size_t idx : {0, 1, 2}) { | 174 | 33.8M | rgb[idx] *= normalizer; | 175 | 33.8M | } | 176 | 11.2M | } |
Unexecuted instantiation: enc_jpeg_data.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: decode.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: quant_weights.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) Unexecuted instantiation: enc_fast_lossless.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float) Unexecuted instantiation: enc_fields.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_group.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_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_progressive_split.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_encoding.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: 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: epf.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: 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: 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: enc_ac_strategy.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_ans.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_chroma_from_luma.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_dot_dictionary.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: enc_external_image.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: blending.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: enc_detect_dots.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) |
177 | | |
178 | | } // namespace jxl |
179 | | |
180 | | #endif // LIB_JXL_CMS_TONE_MAPPING_H_ |