libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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_