color_helpers: refactored eetf2390 tonemapping

This commit is contained in:
Jeremy Selan 2023-05-11 10:32:05 -07:00
parent 156660c72b
commit 941b22f68f
4 changed files with 308 additions and 166 deletions

View file

@ -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<nLut3DSize; ++nBlue )
{
for (int nGreen = 0; nGreen<nLut3DSize; ++nGreen )
{
for (int nRed = 0; nRed<nLut3DSize; ++nRed )
{
glm::vec3 rgb1( nRed * flScale, nGreen * flScale, nBlue * flScale );
glm::vec3 hsv = rgb_to_hsv( rgb1 );
glm::vec3 rgb2 = hsv_to_rgb( hsv );
if ( !approxEqual(rgb1, rgb2 ) )
{
printf("****");
}
printf("%s %s %s\n", glm::to_string(rgb1).c_str(), glm::to_string(hsv).c_str(), glm::to_string(rgb2).c_str() );
}
}
}
}
#endif
#if 0
// Generate a 1d lut
{
int nLutsize = 9;
lut1d_t lut;
lut.resize( nLutsize );
float flScale = 1.f / ( (float) nLutsize - 1.f );
for ( int i = 0; i<nLutsize; ++i )
{
float f = flScale * (float) i;
lut.dataR[i] = f * f;
lut.dataG[i] = f * f;
lut.dataB[i] = f * f;
}
lut.finalize();
glm::vec3 rgb1 = ApplyLut1D_Linear( lut, glm::vec3( -1.f, 0.5f, 2.f ) );
printf("%s\n", glm::to_string(rgb1).c_str() );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%s\n", glm::to_string(rgb2).c_str() );
}
#endif
#if 0
// Generate a 1d lut with a flat spot
{
int nLutsize = 9;
lut1d_t lut;
lut.resize( nLutsize );
float flScale = 1.f / ( (float) nLutsize - 1.f );
printf("LUT\n");
for ( int i = 0; i<nLutsize; ++i )
{
float f = std::max( 0.25f, flScale * (float) i );
lut.dataR[i] = f * f;
lut.dataG[i] = f * f;
lut.dataB[i] = f * f;
printf("%f %d %f\n", f, i, lut.dataG[i] );
}
lut.finalize();
printf("\n");
nLutsize = 21;
flScale = 1.f / ( (float) nLutsize - 1.f );
for (int i=0; i<21; ++i)
{
float f = std::max( 0.25f, flScale * (float) i );
glm::vec3 rgb1 = ApplyLut1D_Linear( lut, glm::vec3( f, f, f ) );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%f %f %f\n", f, rgb1.g, rgb2.g );
}
/*
printf("%s\n", glm::to_string(rgb1).c_str() );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%s\n", glm::to_string(rgb2).c_str() );
*/
}
#endif
return 0;
}

View file

@ -76,17 +76,6 @@ inline T nits_to_pq( const T& nits )
return n;
}
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;
}
// Apply an HDR tonemapping according to eetf 2390 (R-REP-BT.2390-8-2020-PDF-E.pdf)
// sourceXXX == "Mastering Display" == Lw, Lb (in the paper)
// targetXXX == "Target Display" == Lmin, Lmax (in the paper)
@ -96,32 +85,90 @@ inline float eetf_2390_spline( float value, float ks, float maxLum )
// PQ all params first, and undo the output)
// Values outside of 0-1 are not defined
inline float eetf_2390( float valuePQ, float sourceBlackPQ, float sourceWhitePQ, float targetBlackPQ, float targetWhitePQ )
struct eetf_2390_t
{
// normalize PQ based on the mastering (source) display (E1)
const float sourcePQScale = sourceWhitePQ - sourceBlackPQ;
const float invSourcePQScale = 1.f / sourcePQScale;
void init_nits( float sourceBlackNits, float sourceWhiteNits, float targetBlackNits, float targetWhiteNits )
{
init_pq(
nits_to_pq( sourceBlackNits ),
nits_to_pq( sourceWhiteNits ),
nits_to_pq( targetBlackNits ),
nits_to_pq( targetWhiteNits ) );
}
float minLum = ( targetBlackPQ - sourceBlackPQ ) * invSourcePQScale; // TODO: Pull into precomputation?
float maxLum = ( targetWhitePQ - sourceBlackPQ ) * invSourcePQScale; // TODO: Pull into precomputation?
float e1 = ( valuePQ - sourceBlackPQ ) * invSourcePQScale;
void init_pq( float sourceBlackPQ, float sourceWhitePQ, float targetBlackPQ, float targetWhitePQ )
{
m_sourceBlackPQ = sourceBlackPQ;
m_sourcePQScale = sourceWhitePQ - sourceBlackPQ;
m_invSourcePQScale = m_sourcePQScale > 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();

229
src/color_tests.cpp Normal file
View file

@ -0,0 +1,229 @@
#include "color_helpers.h"
#include <cstdio>
/*
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<end; ++i )
{
lut1d[4*i+0] = drm_quantize_lut_value( lut1d_float.dataR[i] );
lut1d[4*i+1] = drm_quantize_lut_value( lut1d_float.dataG[i] );
lut1d[4*i+2] = drm_quantize_lut_value( lut1d_float.dataB[i] );
lut1d[4*i+3] = 0;
}
for ( size_t i=0, end = lut3d_float.data.size(); i<end; ++i )
{
lut3d[4*i+0] = drm_quantize_lut_value( lut3d_float.data[i].r );
lut3d[4*i+1] = drm_quantize_lut_value( lut3d_float.data[i].g );
lut3d[4*i+2] = drm_quantize_lut_value( lut3d_float.data[i].b );
lut3d[4*i+3] = 0;
}
}
}
*/
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<nLut3DSize; ++nBlue )
{
for (int nGreen = 0; nGreen<nLut3DSize; ++nGreen )
{
for (int nRed = 0; nRed<nLut3DSize; ++nRed )
{
glm::vec3 rgb1( nRed * flScale, nGreen * flScale, nBlue * flScale );
glm::vec3 hsv = rgb_to_hsv( rgb1 );
glm::vec3 rgb2 = hsv_to_rgb( hsv );
if ( !approxEqual(rgb1, rgb2 ) )
{
printf("****");
}
printf("%s %s %s\n", glm::to_string(rgb1).c_str(), glm::to_string(hsv).c_str(), glm::to_string(rgb2).c_str() );
}
}
}
}
#endif
#if 0
// Generate a 1d lut
{
int nLutsize = 9;
lut1d_t lut;
lut.resize( nLutsize );
float flScale = 1.f / ( (float) nLutsize - 1.f );
for ( int i = 0; i<nLutsize; ++i )
{
float f = flScale * (float) i;
lut.dataR[i] = f * f;
lut.dataG[i] = f * f;
lut.dataB[i] = f * f;
}
lut.finalize();
glm::vec3 rgb1 = ApplyLut1D_Linear( lut, glm::vec3( -1.f, 0.5f, 2.f ) );
printf("%s\n", glm::to_string(rgb1).c_str() );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%s\n", glm::to_string(rgb2).c_str() );
}
#endif
#if 0
// Generate a 1d lut with a flat spot
{
int nLutsize = 9;
lut1d_t lut;
lut.resize( nLutsize );
float flScale = 1.f / ( (float) nLutsize - 1.f );
printf("LUT\n");
for ( int i = 0; i<nLutsize; ++i )
{
float f = std::max( 0.25f, flScale * (float) i );
lut.dataR[i] = f * f;
lut.dataG[i] = f * f;
lut.dataB[i] = f * f;
printf("%f %d %f\n", f, i, lut.dataG[i] );
}
lut.finalize();
printf("\n");
nLutsize = 21;
flScale = 1.f / ( (float) nLutsize - 1.f );
for (int i=0; i<21; ++i)
{
float f = std::max( 0.25f, flScale * (float) i );
glm::vec3 rgb1 = ApplyLut1D_Linear( lut, glm::vec3( f, f, f ) );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%f %f %f\n", f, rgb1.g, rgb2.g );
}
/*
printf("%s\n", glm::to_string(rgb1).c_str() );
glm::vec3 rgb2 = ApplyLut1D_Inverse_Linear( lut, rgb1 );
printf("%s\n", glm::to_string(rgb2).c_str() );
*/
}
#endif
return 0;
}
void test_eetf2390_mono()
{
printf("%s\n", __func__ );
float vLumaLevels[] = { 0.0, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0, 15000.0 };
// map 0.01 - 10,000 -> 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;
}

View file

@ -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])