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