/src/libjxl/lib/jxl/cms/tone_mapping.h
Line | Count | Source |
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 | 2.96M | : source_range_(source_range), |
29 | 2.96M | target_range_(target_range), |
30 | 2.96M | red_Y_(primaries_luminances[0]), |
31 | 2.96M | green_Y_(primaries_luminances[1]), |
32 | 2.96M | blue_Y_(primaries_luminances[2]) {} |
33 | | |
34 | | // TODO(eustas): test me |
35 | 2.96M | void ToneMap(Color& rgb) const { |
36 | 2.96M | const float luminance = |
37 | 2.96M | source_range_[1] * |
38 | 2.96M | (red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2]); |
39 | 2.96M | const float normalized_pq = |
40 | 2.96M | std::min(1.f, (InvEOTF(luminance) - pq_mastering_min_) * |
41 | 2.96M | inv_pq_mastering_range_); |
42 | 2.96M | const float e2 = (normalized_pq < ks_) ? normalized_pq : P(normalized_pq); |
43 | 2.96M | const float one_minus_e2 = 1 - e2; |
44 | 2.96M | const float one_minus_e2_2 = one_minus_e2 * one_minus_e2; |
45 | 2.96M | const float one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2; |
46 | 2.96M | const float e3 = min_lum_ * one_minus_e2_4 + e2; |
47 | 2.96M | const float e4 = e3 * pq_mastering_range_ + pq_mastering_min_; |
48 | 2.96M | const float d4 = |
49 | 2.96M | TF_PQ_Base::DisplayFromEncoded(/*display_intensity_target=*/1.0, e4); |
50 | 2.96M | const float new_luminance = Clamp1(d4, 0.f, target_range_[1]); |
51 | 2.96M | const float min_luminance = 1e-6f; |
52 | 2.96M | const bool use_cap = (luminance <= min_luminance); |
53 | 2.96M | const float ratio = new_luminance / std::max(luminance, min_luminance); |
54 | 2.96M | const float cap = new_luminance * inv_target_peak_; |
55 | 2.96M | const float multiplier = ratio * normalizer_; |
56 | 8.89M | for (size_t idx : {0, 1, 2}) { |
57 | 8.89M | rgb[idx] = use_cap ? cap : rgb[idx] * multiplier; |
58 | 8.89M | } |
59 | 2.96M | } |
60 | | |
61 | | protected: |
62 | 14.8M | static float InvEOTF(const float luminance) { |
63 | 14.8M | return TF_PQ_Base::EncodedFromDisplay(/*display_intensity_target=*/1.0, |
64 | 14.8M | luminance); |
65 | 14.8M | } |
66 | 2.52M | float T(const float a) const { return (a - ks_) * inv_one_minus_ks_; } |
67 | 2.52M | float P(const float b) const { |
68 | 2.52M | const float t_b = T(b); |
69 | 2.52M | const float t_b_2 = t_b * t_b; |
70 | 2.52M | const float t_b_3 = t_b_2 * t_b; |
71 | 2.52M | return (2 * t_b_3 - 3 * t_b_2 + 1) * ks_ + |
72 | 2.52M | (t_b_3 - 2 * t_b_2 + t_b) * (1 - ks_) + |
73 | 2.52M | (-2 * t_b_3 + 3 * t_b_2) * max_lum_; |
74 | 2.52M | } |
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 | 100k | : HlgOOTF_Base(/*gamma=*/std::pow(1.111f, std::log2(target_luminance / |
105 | 100k | source_luminance)), |
106 | 100k | primaries_luminances) {} |
107 | | |
108 | | // TODO(eustas): test me |
109 | 100k | void Apply(Color& rgb) const { |
110 | 100k | if (!apply_ootf_) return; |
111 | 100k | const float luminance = |
112 | 100k | red_Y_ * rgb[0] + green_Y_ * rgb[1] + blue_Y_ * rgb[2]; |
113 | 100k | const float ratio = std::min<float>(powf(luminance, exponent_), 1e9); |
114 | 100k | rgb[0] *= ratio; |
115 | 100k | rgb[1] *= ratio; |
116 | 100k | rgb[2] *= ratio; |
117 | 100k | } |
118 | | |
119 | | protected: |
120 | | explicit HlgOOTF_Base(float gamma, const Vector3& luminances) |
121 | 105k | : exponent_(gamma - 1), |
122 | 105k | red_Y_(luminances[0]), |
123 | 105k | green_Y_(luminances[1]), |
124 | 105k | 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 | 3.06M | float preserve_saturation = 0.1f) { |
135 | 3.06M | const float luminance = primaries_luminances[0] * rgb[0] + |
136 | 3.06M | primaries_luminances[1] * rgb[1] + |
137 | 3.06M | 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 | 3.06M | float gray_mix_saturation = 0.0f; |
148 | 3.06M | float gray_mix_luminance = 0.0f; |
149 | 9.19M | for (size_t idx : {0, 1, 2}) { |
150 | 9.19M | const float& val = rgb[idx]; |
151 | 9.19M | const float val_minus_gray = val - luminance; |
152 | 9.19M | const float inv_val_minus_gray = |
153 | 9.19M | 1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray); |
154 | 9.19M | const float val_over_val_minus_gray = val * inv_val_minus_gray; |
155 | 9.19M | gray_mix_saturation = |
156 | 9.19M | (val_minus_gray >= 0.0f) |
157 | 9.19M | ? gray_mix_saturation |
158 | 9.19M | : std::max(gray_mix_saturation, val_over_val_minus_gray); |
159 | 9.19M | gray_mix_luminance = |
160 | 9.19M | std::max(gray_mix_luminance, |
161 | 9.19M | (val_minus_gray <= 0.0f) |
162 | 9.19M | ? gray_mix_saturation |
163 | 9.19M | : (val_over_val_minus_gray - inv_val_minus_gray)); |
164 | 9.19M | } |
165 | 3.06M | const float gray_mix = |
166 | 3.06M | Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) + |
167 | 3.06M | gray_mix_luminance), |
168 | 3.06M | 0.0f, 1.0f); |
169 | 9.19M | for (size_t idx : {0, 1, 2}) { |
170 | 9.19M | float& val = rgb[idx]; |
171 | 9.19M | val = gray_mix * (luminance - val) + val; |
172 | 9.19M | } |
173 | 3.06M | const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]}); |
174 | 3.06M | const float normalizer = 1.0f / max_clr; |
175 | 9.19M | for (size_t idx : {0, 1, 2}) { |
176 | 9.19M | rgb[idx] *= normalizer; |
177 | 9.19M | } |
178 | 3.06M | } encode.cc:jxl::GamutMapScalar(std::__1::array<float, 3ul>&, std::__1::array<float, 3ul> const&, float) Line | Count | Source | 134 | 3.06M | float preserve_saturation = 0.1f) { | 135 | 3.06M | const float luminance = primaries_luminances[0] * rgb[0] + | 136 | 3.06M | primaries_luminances[1] * rgb[1] + | 137 | 3.06M | 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 | 3.06M | float gray_mix_saturation = 0.0f; | 148 | 3.06M | float gray_mix_luminance = 0.0f; | 149 | 9.19M | for (size_t idx : {0, 1, 2}) { | 150 | 9.19M | const float& val = rgb[idx]; | 151 | 9.19M | const float val_minus_gray = val - luminance; | 152 | 9.19M | const float inv_val_minus_gray = | 153 | 9.19M | 1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray); | 154 | 9.19M | const float val_over_val_minus_gray = val * inv_val_minus_gray; | 155 | 9.19M | gray_mix_saturation = | 156 | 9.19M | (val_minus_gray >= 0.0f) | 157 | 9.19M | ? gray_mix_saturation | 158 | 9.19M | : std::max(gray_mix_saturation, val_over_val_minus_gray); | 159 | 9.19M | gray_mix_luminance = | 160 | 9.19M | std::max(gray_mix_luminance, | 161 | 9.19M | (val_minus_gray <= 0.0f) | 162 | 9.19M | ? gray_mix_saturation | 163 | 9.19M | : (val_over_val_minus_gray - inv_val_minus_gray)); | 164 | 9.19M | } | 165 | 3.06M | const float gray_mix = | 166 | 3.06M | Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) + | 167 | 3.06M | gray_mix_luminance), | 168 | 3.06M | 0.0f, 1.0f); | 169 | 9.19M | for (size_t idx : {0, 1, 2}) { | 170 | 9.19M | float& val = rgb[idx]; | 171 | 9.19M | val = gray_mix * (luminance - val) + val; | 172 | 9.19M | } | 173 | 3.06M | const float max_clr = std::max({1.0f, rgb[0], rgb[1], rgb[2]}); | 174 | 3.06M | const float normalizer = 1.0f / max_clr; | 175 | 9.19M | for (size_t idx : {0, 1, 2}) { | 176 | 9.19M | rgb[idx] *= normalizer; | 177 | 9.19M | } | 178 | 3.06M | } |
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_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: image_bundle.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: render_pipeline_stage.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) |
179 | | |
180 | | } // namespace jxl |
181 | | |
182 | | #endif // LIB_JXL_CMS_TONE_MAPPING_H_ |