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