summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h')
-rw-r--r--third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h179
1 files changed, 179 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h b/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
new file mode 100644
index 0000000000..a114109ea6
--- /dev/null
+++ b/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
@@ -0,0 +1,179 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_JXL_CMS_TONE_MAPPING_H_
+#define LIB_JXL_CMS_TONE_MAPPING_H_
+
+#include <algorithm>
+#include <cmath>
+#include <utility>
+
+#include "lib/jxl/base/common.h"
+#include "lib/jxl/base/compiler_specific.h"
+#include "lib/jxl/cms/transfer_functions.h"
+
+namespace jxl {
+
+class Rec2408ToneMapperBase {
+ public:
+ explicit Rec2408ToneMapperBase(std::pair<float, float> source_range,
+ std::pair<float, float> target_range,
+ const float primaries_luminances[3])
+ : source_range_(source_range),
+ target_range_(target_range),
+ red_Y_(primaries_luminances[0]),
+ green_Y_(primaries_luminances[1]),
+ blue_Y_(primaries_luminances[2]) {}
+
+ // TODO(eustas): test me
+ void ToneMap(float* red, float* green, float* blue) const {
+ const float luminance =
+ source_range_.second *
+ (red_Y_ * *red + green_Y_ * *green + blue_Y_ * *blue);
+ const float normalized_pq =
+ std::min(1.f, (InvEOTF(luminance) - pq_mastering_min_) *
+ inv_pq_mastering_range_);
+ const float e2 = (normalized_pq < ks_) ? normalized_pq : P(normalized_pq);
+ const float one_minus_e2 = 1 - e2;
+ const float one_minus_e2_2 = one_minus_e2 * one_minus_e2;
+ const float one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
+ const float e3 = min_lum_ * one_minus_e2_4 + e2;
+ const float e4 = e3 * pq_mastering_range_ + pq_mastering_min_;
+ const float d4 =
+ TF_PQ_Base::DisplayFromEncoded(/*display_intensity_target=*/1.0, e4);
+ const float new_luminance = Clamp1(d4, 0.f, target_range_.second);
+ const float min_luminance = 1e-6f;
+ const bool use_cap = (luminance <= min_luminance);
+ const float ratio = new_luminance / std::max(luminance, min_luminance);
+ const float cap = new_luminance * inv_target_peak_;
+ const float multiplier = ratio * normalizer_;
+ for (float* const val : {red, green, blue}) {
+ *val = use_cap ? cap : *val * multiplier;
+ }
+ }
+
+ protected:
+ float InvEOTF(const float luminance) const {
+ return TF_PQ_Base::EncodedFromDisplay(/*display_intensity_target=*/1.0,
+ luminance);
+ }
+ float T(const float a) const { return (a - ks_) * inv_one_minus_ks_; }
+ float P(const float b) const {
+ const float t_b = T(b);
+ const float t_b_2 = t_b * t_b;
+ const float t_b_3 = t_b_2 * t_b;
+ return (2 * t_b_3 - 3 * t_b_2 + 1) * ks_ +
+ (t_b_3 - 2 * t_b_2 + t_b) * (1 - ks_) +
+ (-2 * t_b_3 + 3 * t_b_2) * max_lum_;
+ }
+
+ const std::pair<float, float> source_range_;
+ const std::pair<float, float> target_range_;
+ const float red_Y_;
+ const float green_Y_;
+ const float blue_Y_;
+
+ const float pq_mastering_min_ = InvEOTF(source_range_.first);
+ const float pq_mastering_max_ = InvEOTF(source_range_.second);
+ const float pq_mastering_range_ = pq_mastering_max_ - pq_mastering_min_;
+ const float inv_pq_mastering_range_ = 1.0f / pq_mastering_range_;
+ // TODO(eustas): divide instead of inverse-multiply?
+ const float min_lum_ = (InvEOTF(target_range_.first) - pq_mastering_min_) *
+ inv_pq_mastering_range_;
+ // TODO(eustas): divide instead of inverse-multiply?
+ const float max_lum_ = (InvEOTF(target_range_.second) - pq_mastering_min_) *
+ inv_pq_mastering_range_;
+ const float ks_ = 1.5f * max_lum_ - 0.5f;
+
+ const float inv_one_minus_ks_ = 1.0f / std::max(1e-6f, 1.0f - ks_);
+
+ const float normalizer_ = source_range_.second / target_range_.second;
+ const float inv_target_peak_ = 1.f / target_range_.second;
+};
+
+class HlgOOTF_Base {
+ public:
+ explicit HlgOOTF_Base(float source_luminance, float target_luminance,
+ const float primaries_luminances[3])
+ : HlgOOTF_Base(/*gamma=*/std::pow(1.111f, std::log2(target_luminance /
+ source_luminance)),
+ primaries_luminances) {}
+
+ // TODO(eustas): test me
+ void Apply(float* red, float* green, float* blue) const {
+ if (!apply_ootf_) return;
+ const float luminance = red_Y_ * *red + green_Y_ * *green + blue_Y_ * *blue;
+ const float ratio = std::min<float>(powf(luminance, exponent_), 1e9);
+ *red *= ratio;
+ *green *= ratio;
+ *blue *= ratio;
+ }
+
+ protected:
+ explicit HlgOOTF_Base(float gamma, const float luminances[3])
+ : exponent_(gamma - 1),
+ red_Y_(luminances[0]),
+ green_Y_(luminances[1]),
+ blue_Y_(luminances[2]) {}
+ const float exponent_;
+ const bool apply_ootf_ = exponent_ < -0.01f || 0.01f < exponent_;
+ const float red_Y_;
+ const float green_Y_;
+ const float blue_Y_;
+};
+
+static JXL_MAYBE_UNUSED void GamutMapScalar(float* red, float* green,
+ float* blue,
+ const float primaries_luminances[3],
+ float preserve_saturation = 0.1f) {
+ const float luminance = primaries_luminances[0] * *red +
+ primaries_luminances[1] * *green +
+ primaries_luminances[2] * *blue;
+
+ // Desaturate out-of-gamut pixels. This is done by mixing each pixel
+ // with just enough gray of the target luminance to make all
+ // components non-negative.
+ // - For saturation preservation, if a component is still larger than
+ // 1 then the pixel is normalized to have a maximum component of 1.
+ // That will reduce its luminance.
+ // - For luminance preservation, getting all components below 1 is
+ // done by mixing in yet more gray. That will desaturate it further.
+ float gray_mix_saturation = 0.0f;
+ float gray_mix_luminance = 0.0f;
+ for (const float* ch : {red, green, blue}) {
+ const float& val = *ch;
+ const float val_minus_gray = val - luminance;
+ const float inv_val_minus_gray =
+ 1.0f / ((val_minus_gray == 0.0f) ? 1.0f : val_minus_gray);
+ const float val_over_val_minus_gray = val * inv_val_minus_gray;
+ gray_mix_saturation =
+ (val_minus_gray >= 0.0f)
+ ? gray_mix_saturation
+ : std::max(gray_mix_saturation, val_over_val_minus_gray);
+ gray_mix_luminance =
+ std::max(gray_mix_luminance,
+ (val_minus_gray <= 0.0f)
+ ? gray_mix_saturation
+ : (val_over_val_minus_gray - inv_val_minus_gray));
+ }
+ const float gray_mix =
+ Clamp1((preserve_saturation * (gray_mix_saturation - gray_mix_luminance) +
+ gray_mix_luminance),
+ 0.0f, 1.0f);
+ for (float* const ch : {red, green, blue}) {
+ float& val = *ch;
+ val = gray_mix * (luminance - val) + val;
+ }
+ const float max_clr = std::max({1.0f, *red, *green, *blue});
+ const float normalizer = 1.0f / max_clr;
+ for (float* const ch : {red, green, blue}) {
+ float& val = *ch;
+ val *= normalizer;
+ }
+}
+
+} // namespace jxl
+
+#endif // LIB_JXL_CMS_TONE_MAPPING_H_