// 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. #include "lib/jxl/render_pipeline/stage_from_linear.h" #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_from_linear.cc" #include #include #include "lib/jxl/dec_tone_mapping-inl.h" #include "lib/jxl/sanitizers.h" #include "lib/jxl/transfer_functions-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { namespace { // These templates are not found via ADL. using hwy::HWY_NAMESPACE::IfThenZeroElse; template struct PerChannelOp { explicit PerChannelOp(Op op) : op(op) {} template void Transform(D d, T* r, T* g, T* b) const { *r = op.Transform(d, *r); *g = op.Transform(d, *g); *b = op.Transform(d, *b); } Op op; }; template PerChannelOp MakePerChannelOp(Op&& op) { return PerChannelOp(std::forward(op)); } struct OpLinear { template T Transform(D d, const T& linear) const { return linear; } }; struct OpRgb { template T Transform(D d, const T& linear) const { #if JXL_HIGH_PRECISION return TF_SRGB().EncodedFromDisplay(d, linear); #else return FastLinearToSRGB(d, linear); #endif } }; struct OpPq { template T Transform(D d, const T& linear) const { return TF_PQ().EncodedFromDisplay(d, linear); } }; struct OpHlg { explicit OpHlg(const float luminances[3], const float intensity_target) : hlg_ootf_(HlgOOTF::ToSceneLight(/*display_luminance=*/intensity_target, luminances)) {} template void Transform(D d, T* r, T* g, T* b) const { hlg_ootf_.Apply(r, g, b); *r = TF_HLG().EncodedFromDisplay(d, *r); *g = TF_HLG().EncodedFromDisplay(d, *g); *b = TF_HLG().EncodedFromDisplay(d, *b); } HlgOOTF hlg_ootf_; }; struct Op709 { template T Transform(D d, const T& linear) const { return TF_709().EncodedFromDisplay(d, linear); } }; struct OpGamma { const float inverse_gamma; template T Transform(D d, const T& linear) const { return IfThenZeroElse(Le(linear, Set(d, 1e-5f)), FastPowf(d, linear, Set(d, inverse_gamma))); } }; template class FromLinearStage : public RenderPipelineStage { public: explicit FromLinearStage(Op op) : RenderPipelineStage(RenderPipelineStage::Settings()), op_(std::move(op)) {} void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows, size_t xextra, size_t xsize, size_t xpos, size_t ypos, size_t thread_id) const final { PROFILER_ZONE("FromLinear"); const HWY_FULL(float) d; const size_t xsize_v = RoundUpTo(xsize, Lanes(d)); float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0); float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0); float* JXL_RESTRICT row2 = GetInputRow(input_rows, 2, 0); // All calculations are lane-wise, still some might require // value-dependent behaviour (e.g. NearestInt). Temporary unpoison last // vector tail. msan::UnpoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize)); msan::UnpoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize)); msan::UnpoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize)); for (ssize_t x = -xextra; x < (ssize_t)(xsize + xextra); x += Lanes(d)) { auto r = LoadU(d, row0 + x); auto g = LoadU(d, row1 + x); auto b = LoadU(d, row2 + x); op_.Transform(d, &r, &g, &b); StoreU(r, d, row0 + x); StoreU(g, d, row1 + x); StoreU(b, d, row2 + x); } msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize)); msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize)); msan::PoisonMemory(row2 + xsize, sizeof(float) * (xsize_v - xsize)); } RenderPipelineChannelMode GetChannelMode(size_t c) const final { return c < 3 ? RenderPipelineChannelMode::kInPlace : RenderPipelineChannelMode::kIgnored; } const char* GetName() const override { return "FromLinear"; } private: Op op_; }; template std::unique_ptr> MakeFromLinearStage(Op&& op) { return jxl::make_unique>(std::forward(op)); } std::unique_ptr GetFromLinearStage( const OutputEncodingInfo& output_encoding_info) { if (output_encoding_info.color_encoding.tf.IsLinear()) { return MakeFromLinearStage(MakePerChannelOp(OpLinear())); } else if (output_encoding_info.color_encoding.tf.IsSRGB()) { return MakeFromLinearStage(MakePerChannelOp(OpRgb())); } else if (output_encoding_info.color_encoding.tf.IsPQ()) { return MakeFromLinearStage(MakePerChannelOp(OpPq())); } else if (output_encoding_info.color_encoding.tf.IsHLG()) { return MakeFromLinearStage( OpHlg(output_encoding_info.luminances, output_encoding_info.desired_intensity_target)); } else if (output_encoding_info.color_encoding.tf.Is709()) { return MakeFromLinearStage(MakePerChannelOp(Op709())); } else if (output_encoding_info.color_encoding.tf.IsGamma() || output_encoding_info.color_encoding.tf.IsDCI()) { return MakeFromLinearStage( MakePerChannelOp(OpGamma{output_encoding_info.inverse_gamma})); } else { // This is a programming error. JXL_ABORT("Invalid target encoding"); } } } // namespace // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); #if HWY_ONCE namespace jxl { HWY_EXPORT(GetFromLinearStage); std::unique_ptr GetFromLinearStage( const OutputEncodingInfo& output_encoding_info) { return HWY_DYNAMIC_DISPATCH(GetFromLinearStage)(output_encoding_info); } } // namespace jxl #endif