From 941b22f68f8aa37359f6dab5774f6da61747d50a Mon Sep 17 00:00:00 2001 From: Jeremy Selan Date: Thu, 11 May 2023 10:32:05 -0700 Subject: [PATCH] color_helpers: refactored eetf2390 tonemapping --- src/color_helpers.cpp | 134 ------------------------ src/color_helpers.h | 109 ++++++++++++++------ src/color_tests.cpp | 229 ++++++++++++++++++++++++++++++++++++++++++ src/meson.build | 2 + 4 files changed, 308 insertions(+), 166 deletions(-) create mode 100644 src/color_tests.cpp diff --git a/src/color_helpers.cpp b/src/color_helpers.cpp index 57b89ff..06568a8 100644 --- a/src/color_helpers.cpp +++ b/src/color_helpers.cpp @@ -850,137 +850,3 @@ bool approxEqual( const glm::vec3 & a, const glm::vec3 & b, float flTolerance = glm::vec3 v = glm::abs(a - b); return ( v.x < flTolerance && v.y < flTolerance && v.z < flTolerance ); } - -int color_tests() -{ -#if 0 - { - // Test normalized primary matrix - primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }; - glm::vec2 white = { 0.3070f, 0.3220f }; - - glm::mat3x3 rgb_to_xyz = normalised_primary_matrix( primaries, white, 1.f ); - printf("normalised_primary_matrix rgb_to_xyz %s\n", glm::to_string(rgb_to_xyz).c_str() ); - - glm::vec3 redXYZ = rgb_to_xyz * glm::vec3(1,0,0); - glm::vec2 redxy = XYZ_to_xy( redXYZ ); - glm::vec3 whiteXYZ = rgb_to_xyz * glm::vec3(1); - glm::vec2 whitexy = XYZ_to_xy( whiteXYZ ); - - printf("r xy %s == %s \n", glm::to_string(primaries.r).c_str(), glm::to_string(redxy).c_str() ); - printf("w xy %s == %s \n", glm::to_string(white).c_str(), glm::to_string(whitexy).c_str() ); - } -#endif - - -#if 0 - { - // chromatic adapatation - glm::vec3 d50XYZ = glm::vec3(0.96422f, 1.00000f, 0.82521f ); - glm::vec3 d65XYZ = glm::vec3(0.95047f, 1.00000f, 1.08883f ); - printf("d50XYZ %s\n", glm::to_string(d50XYZ).c_str() ); - printf("d65XYZ %s\n", glm::to_string(d65XYZ).c_str() ); - { - - glm::mat3x3 d65From50 = chromatic_adaptation_matrix( d50XYZ, d65XYZ, k_EChromaticAdapatationMethod_Bradford ); - printf("bradford d65From50 %s\n", glm::to_string(d65From50).c_str() ); - glm::vec3 d65_2 = d65From50 * d50XYZ; - printf("bradford d65_2 %s\n", glm::to_string(d65_2).c_str() ); - } - { - - glm::mat3x3 d65From50 = chromatic_adaptation_matrix( d50XYZ, d65XYZ, k_EChromaticAdapatationMethod_XYZ ); - printf("xyzscaling d65From50 %s\n", glm::to_string(d65From50).c_str() ); - glm::vec3 d65_2 = d65From50 * d50XYZ; - printf("xyzscaling d65_2 %s\n", glm::to_string(d65_2).c_str() ); - } - } -#endif - -#if 0 - { - - int nLut3DSize = 4; - float flScale = 1.f / ( (float) nLut3DSize - 1.f ); - for (int nBlue = 0; nBlue 0.f ? 1.f / m_sourcePQScale : 0.f; + m_minLumPQ = ( targetBlackPQ - sourceBlackPQ ) * m_invSourcePQScale; + m_maxLumPQ = ( targetWhitePQ - sourceBlackPQ ) * m_invSourcePQScale; + m_ks = 1.5 * m_maxLumPQ - 0.5; // TODO : return false if ks == 1.f? + } - // Knee - float ks = 1.5 * maxLum - 0.5; // TODO: Pull into precomputation? - float b = minLum; + // "Max RGB" approach, as defined in "Color Volume and Hue-Preservation in HDR Tone Mapping" + // Digital Object Identifier 10.5594/JMI.2020.2984046 + // Date of publication: 4 May 2020 + inline glm::vec3 apply_max_rgb( const glm::vec3 & inputNits ) + { + float input_scalar_nits = std::max( inputNits.r, std::max( inputNits.g, inputNits.b ) ); + float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; + return inputNits * gain; + } - // Apply high end rolloff - float e2 = e1 < ks ? e1 : eetf_2390_spline( e1, ks, maxLum ); + inline glm::vec3 apply_luma_rgb( const glm::vec3 & inputNits ) + { + float input_scalar_nits = 0.2627f * inputNits.r + 0.6780f * inputNits.g + 0.0593f * inputNits.b; + float output_scalar_nits = pq_to_nits( apply_pq( nits_to_pq( input_scalar_nits ) ) ); + float gain = input_scalar_nits > 0.f ? output_scalar_nits / input_scalar_nits : 0.f; + return inputNits * gain; + } - // Apply low end pedestal - float one_min_e2 = 1.f - e2; - float one_min_e2_sq = one_min_e2 * one_min_e2; - float e3 = e2 + b * one_min_e2_sq * one_min_e2_sq; + inline glm::vec3 apply_independent_rgb( const glm::vec3 & inputNits ) + { + glm::vec3 inputPQ = nits_to_pq( inputNits ); + glm::vec3 outputPQ = { apply_pq( inputPQ.r ), apply_pq( inputPQ.g ), apply_pq( inputPQ.b ) }; + return pq_to_nits( outputPQ ); + } - // Re-apply mastering (source) transform - return e3 * sourcePQScale + sourceBlackPQ; -} + float m_sourceBlackPQ = 0.f; + float m_sourcePQScale = 0.f; + float m_invSourcePQScale = 0.f; + float m_minLumPQ = 0.f; + float m_maxLumPQ = 0.f; + float m_ks = 0.f; + // Raw PQ transfer function + inline float apply_pq( float valuePQ ) + { + // normalize PQ based on the mastering (source) display (E1) + float e1 = ( valuePQ - m_sourceBlackPQ ) * m_invSourcePQScale; + + // Apply high end rolloff + float e2 = e1 < m_ks ? e1 : _eetf_2390_spline( e1, m_ks, m_maxLumPQ ); + + // Apply low end pedestal + float one_min_e2 = 1.f - e2; + float one_min_e2_sq = one_min_e2 * one_min_e2; + float e3 = e2 + m_minLumPQ * one_min_e2_sq * one_min_e2_sq; + + // Re-apply mastering (source) transform + return e3 * m_sourcePQScale + m_sourceBlackPQ; + } + + private: + inline float _eetf_2390_spline( float value, float ks, float maxLum ) + { + float t = ( value - ks ) / ( 1.f - ks ); // TODO : guard against ks == 1.f? + float t_sq = t*t; + float t_cub = t_sq*t; + float v1 = ( 2.f * t_cub - 3.f * t_sq + 1.f ) * ks; + float v2 = ( t_cub - 2.f * t_sq + t ) * ( 1.f - ks ); + float v3 = (-2.f * t_cub + 3.f * t_sq ) * maxLum; + return v1 + v2 + v3; + } +}; inline float flerp( float a, float b, float t ) { @@ -336,5 +383,3 @@ static constexpr displaycolorimetry_t displaycolorimetry_2020 .primaries = { { 0.708f, 0.292f }, { 0.170f, 0.797f }, { 0.131f, 0.046f } }, .white = { 0.3127f, 0.3290f }, // D65 }; - -int color_tests(); \ No newline at end of file diff --git a/src/color_tests.cpp b/src/color_tests.cpp new file mode 100644 index 0000000..51e5dab --- /dev/null +++ b/src/color_tests.cpp @@ -0,0 +1,229 @@ +#include "color_helpers.h" +#include + +/* +const uint32_t nLutSize1d = 4096; +const uint32_t nLutEdgeSize3d = 17; + +uint16_t lut1d[nLutSize1d*4]; +uint16_t lut3d[nLutEdgeSize3d*nLutEdgeSize3d*nLutEdgeSize3d*4]; + +lut1d_t lut1d_float; +lut3d_t lut3d_float; + +static void BenchmarkCalcColorTransform(EOTF inputEOTF, benchmark::State &state) +{ + const primaries_t primaries = { { 0.602f, 0.355f }, { 0.340f, 0.574f }, { 0.164f, 0.121f } }; + const glm::vec2 white = { 0.3070f, 0.3220f }; + + displaycolorimetry_t inputColorimetry{}; + inputColorimetry.primaries = primaries; + inputColorimetry.white = white; + + displaycolorimetry_t outputEncodingColorimetry{}; + outputEncodingColorimetry.primaries = primaries; + outputEncodingColorimetry.white = white; + + colormapping_t colorMapping{}; + + tonemapping_t tonemapping{}; + tonemapping.bUseShaper = true; + + nightmode_t nightmode{}; + float flGain = 1.0f; + + for (auto _ : state) { + calcColorTransform( &lut1d_float, nLutSize1d, &lut3d_float, nLutEdgeSize3d, inputColorimetry, inputEOTF, + outputEncodingColorimetry, EOTF_Gamma22, + colorMapping, nightmode, tonemapping, nullptr, flGain ); + for ( size_t i=0, end = lut1d_float.dataR.size(); i 0.1 - 1000 + float sourceBlackNits = 0.01f; + float sourceWhiteNits = 5000.0f; + float sourceBlackPQ = nits_to_pq( sourceBlackNits ); + float sourceWhitePQ = nits_to_pq( sourceWhiteNits ); + + printf("source\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f \n", sourceBlackNits, sourceWhiteNits, sourceBlackPQ * 1023.f, sourceWhitePQ * 1023.f ); + + float destBlackNits = 0.1f; + float destWhiteNits = 1000.0f; + float destBlackPQ = nits_to_pq( destBlackNits ); + float destWhitePQ = nits_to_pq( destWhiteNits ); + printf("dest\t%0.02f - %0.01f\t\tPQ10: %0.1f %0.1f\n", destBlackNits, destWhiteNits, destBlackPQ * 1023.f, destWhitePQ * 1023.f ); + printf("\n"); + + eetf_2390_t eetf; + eetf.init_pq( sourceBlackPQ, sourceWhitePQ, destBlackPQ, destWhitePQ ); + + for ( size_t nLevel=0; nLevel < 12; ++nLevel ) + { + float flInputNits = vLumaLevels[nLevel]; + float inputPQ = nits_to_pq( flInputNits ); + float tonemappedOutputPQ = eetf.apply_pq( inputPQ ); + float tonemappedOutputNits = pq_to_nits( tonemappedOutputPQ ); + printf("value\t%0.03f -> %0.03f\tPQ10: %0.1f -> %0.1f\n", flInputNits, tonemappedOutputNits, inputPQ * 1023.f, tonemappedOutputPQ * 1023.f ); + } +} + +int main(int argc, char* argv[]) +{ + printf("color_tests\n"); + test_eetf2390_mono(); + return 0; +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 6fbce7e..3ceb8d2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -124,3 +124,5 @@ endif benchmark_dep = dependency('benchmark') executable('gamescope_color_microbench', ['color_bench.cpp', 'color_helpers.cpp'], dependencies:[benchmark_dep, glm_dep]) + +executable('gamescope_color_tests', ['color_tests.cpp', 'color_helpers.cpp'], dependencies:[glm_dep])