#include "color_helpers.h" #include #include #include #include #include #include #include #include glm::vec3 xyY_to_XYZ( const glm::vec2 & xy, float Y ) { if ( fabsf( xy.y ) < std::numeric_limits::min() ) { return glm::vec3( 0.f ); } else { return glm::vec3( Y * xy.x / xy.y, Y, ( 1.f - xy.x - xy.y ) * Y / xy.y ); } } glm::vec2 XYZ_to_xy( const glm::vec3 & XYZ ) { float sum = ( XYZ.x + XYZ.y + XYZ.z ); if ( fabsf( sum ) < std::numeric_limits::min() ) { return glm::vec2( 0.f ); } else { return glm::vec2( XYZ.x / sum, XYZ.y / sum ); } } glm::vec3 xy_to_xyz( const glm::vec2 & xy ) { return glm::vec3( xy.x, xy.y, 1.f - xy.x - xy.y ); } // Convert xy to CIE 1976 u'v' glm::vec2 xy_to_uv( const glm::vec2 & xy ) { float denom = -2.f * xy.x + 12.f * xy.y + 3.f; if ( fabsf( denom ) < std::numeric_limits::min() ) { return glm::vec2( 0.f ); } return glm::vec2( 4.f * xy.x / denom, 9.f * xy.y / denom ); } // Convert CIE 1976 u'v' to xy glm::vec2 uv_to_xy( const glm::vec2 & uv ) { float denom = 6.f * uv.x - 16.f * uv.y + 12.f; if ( fabsf( denom ) < std::numeric_limits::min() ) { return glm::vec2( 0.f ); } return glm::vec2( 9.f * uv.x / denom, 4.f * uv.y / denom ); } glm::mat3 normalised_primary_matrix( const primaries_t & rgbPrimaries, const glm::vec2 & whitePrimary, float whiteLuminance ) { glm::mat3 matPrimaries( xy_to_xyz( rgbPrimaries.r ), xy_to_xyz( rgbPrimaries.g ), xy_to_xyz( rgbPrimaries.b ) ); glm::vec3 whiteXYZ = xyY_to_XYZ( whitePrimary, whiteLuminance ); glm::mat3 whiteScale = glm::diagonal3x3( glm::inverse( matPrimaries ) * whiteXYZ ); return matPrimaries * whiteScale; } enum EChromaticAdaptationMethod { k_EChromaticAdapatationMethod_XYZ, k_EChromaticAdapatationMethod_Bradford, }; glm::mat3 chromatic_adaptation_matrix( const glm::vec3 & sourceWhite, const glm::vec3 & destWhite, EChromaticAdaptationMethod eMethod ) { static const glm::mat3 k_matBradford( 0.8951f,-0.7502f, 0.0389f, 0.2664f,1.7135f, -0.0685f, -0.1614f, 0.0367f, 1.0296f ); glm::mat3 matAdaptation = eMethod == k_EChromaticAdapatationMethod_XYZ ? glm::diagonal3x3( glm::vec3(1,1,1) ) : k_matBradford; glm::vec3 coneResponseDest = matAdaptation * destWhite; glm::vec3 coneResponseSource = matAdaptation * sourceWhite; glm::vec3 scale = glm::vec3( coneResponseDest.x / coneResponseSource.x, coneResponseDest.y / coneResponseSource.y, coneResponseDest.z / coneResponseSource.z ); return glm::inverse( matAdaptation ) * glm::diagonal3x3( scale ) * matAdaptation; } displaycolorimetry_t lerp( const displaycolorimetry_t & a, const displaycolorimetry_t & b, float t ) { displaycolorimetry_t result; primaries_t a_uv, b_uv; a_uv.r = xy_to_uv( a.primaries.r ); a_uv.g = xy_to_uv( a.primaries.g ); a_uv.b = xy_to_uv( a.primaries.b ); b_uv.r = xy_to_uv( b.primaries.r ); b_uv.g = xy_to_uv( b.primaries.g ); b_uv.b = xy_to_uv( b.primaries.b ); glm::vec2 a_white = xy_to_uv( a.white ); glm::vec2 b_white = xy_to_uv( b.white ); result.primaries.r.x = flerp( a_uv.r.x, b_uv.r.x, t ); result.primaries.r.y = flerp( a_uv.r.y, b_uv.r.y, t ); result.primaries.g.x = flerp( a_uv.g.x, b_uv.g.x, t ); result.primaries.g.y = flerp( a_uv.g.y, b_uv.g.y, t ); result.primaries.b.x = flerp( a_uv.b.x, b_uv.b.x, t ); result.primaries.b.y = flerp( a_uv.b.y, b_uv.b.y, t ); result.white.x = flerp( a_white.x, b_white.x, t ); result.white.y = flerp( a_white.y, b_white.y, t ); result.primaries.r = uv_to_xy( result.primaries.r ); result.primaries.g = uv_to_xy( result.primaries.g ); result.primaries.b = uv_to_xy( result.primaries.b ); result.white = uv_to_xy( result.white ); result.eotf = ( t > 0.5f ) ? b.eotf : a.eotf; return result; } colormapping_t lerp( const colormapping_t & a, const colormapping_t & b, float t ) { colormapping_t result; result.blendEnableMinSat = flerp( a.blendEnableMinSat, b.blendEnableMinSat, t ); result.blendEnableMaxSat = flerp( a.blendEnableMaxSat, b.blendEnableMaxSat, t ); result.blendAmountMin = flerp( a.blendAmountMin, b.blendAmountMin, t ); result.blendAmountMax = flerp( a.blendAmountMax, b.blendAmountMax, t ); return result; } glm::vec3 hsv_to_rgb( const glm::vec3 & hsv ) { if ( fabsf( hsv.y ) < std::numeric_limits::min() ) { return glm::vec3( hsv.z ) ; } float flHue = positive_mod( hsv.x, 1.f ); flHue *= 6.f; int i = flHue; // integer part float f = flHue - i; // fractional part float p = hsv.z * ( 1.f - hsv.y ); float q = hsv.z * ( 1.f - hsv.y * f ); float t = hsv.z * ( 1.f - hsv.y * ( 1.f - f ) ); switch(i) { case 0: return glm::vec3( hsv.z, t, p ); break; case 1: return glm::vec3( q, hsv.z, p ); break; case 2: return glm::vec3( p, hsv.z, t ); break; case 3: return glm::vec3( p, q, hsv.z ); break; case 4: return glm::vec3( t, p, hsv.z ); break; case 5: return glm::vec3( hsv.z, p, q ); break; } return glm::vec3( 0 ); } glm::vec3 rgb_to_hsv( const glm::vec3 & rgb ) { float flMax = std::max( std::max( rgb.x, rgb.y ), rgb.z ); float flMin = std::min( std::min( rgb.x, rgb.y ), rgb.z ); float flDelta = flMax - flMin; glm::vec3 hsv; hsv.y = ( fabsf( flMax ) < std::numeric_limits::min() ) ? 0.f : flDelta / flMax; hsv.z = flMax; if (hsv.y == 0.f) { hsv.x = -1.0f; } else { if ( rgb.x == flMax ) { hsv.x = (rgb.y - rgb.z) / flDelta; } else if ( rgb.y == flMax ) { hsv.x = 2.f + ( rgb.z - rgb.x ) / flDelta; } else { hsv.x = 4.f + ( rgb.x - rgb.y ) / flDelta; } hsv.x /= 6.f; if ( hsv.x < 0.f ) { hsv.x += 1.f; } } return hsv; } bool BOutOfGamut( const glm::vec3 & color ) { return ( color.x<0.f || color.x > 1.f || color.y<0.f || color.y > 1.f || color.z<0.f || color.z > 1.f ); } void calcColorTransform( uint16_t * pRgbxData1d, int nLutSize1d, uint16_t * pRgbxData3d, int nLutEdgeSize3d, const displaycolorimetry_t & source, const displaycolorimetry_t & dest, const colormapping_t & mapping, const nightmode_t & nightmode, EOTF contentEOTF ) { glm::mat3 xyz_from_dest = normalised_primary_matrix( dest.primaries, dest.white, 1.f ); glm::mat3 dest_from_xyz = glm::inverse( xyz_from_dest ); glm::mat3 xyz_from_source = normalised_primary_matrix( source.primaries, source.white, 1.f ); glm::mat3 dest_from_source = dest_from_xyz * xyz_from_source; // Absolute colorimetric mapping // Generate shaper lut (for now, identity) // TODO: if source EOTF doest match dest EOTF, generate a proper shaper lut (and the inverse) // and then compute a shaper-aware 3DLUT if ( pRgbxData1d ) { float flScale = 1.f / ( (float) nLutSize1d - 1.f ); for ( int nVal=0; nVal COLOR 0.5 // Generic wide gamut display --> COLOR 1.0 displaycolorimetry_t wideGamutGeneric; wideGamutGeneric.primaries = { { 0.6825f, 0.3165f }, { 0.241f, 0.719f }, { 0.138f, 0.050f } }; wideGamutGeneric.white = { 0.3127f, 0.3290f }; // D65 wideGamutGeneric.eotf = EOTF::Gamma22; // Assume linear saturation computation colormapping_t mapSmoothToCubeLinearSat; mapSmoothToCubeLinearSat.blendEnableMinSat = 0.7f; mapSmoothToCubeLinearSat.blendEnableMaxSat = 1.0f; mapSmoothToCubeLinearSat.blendAmountMin = 0.0f; mapSmoothToCubeLinearSat.blendAmountMax = 1.f; // Assume linear saturation computation colormapping_t mapPartialToCubeLinearSat; mapPartialToCubeLinearSat.blendEnableMinSat = 0.7f; mapPartialToCubeLinearSat.blendEnableMaxSat = 1.0f; mapPartialToCubeLinearSat.blendAmountMin = 0.0f; mapPartialToCubeLinearSat.blendAmountMax = 0.25; if ( flSDRGamutWideness < 0.5f ) { float t = cfit( flSDRGamutWideness, 0.f, 0.5f, 0.0f, 1.0f ); *pSynetheticInputColorimetry = lerp( nativeDisplayOutput, wideGamutGeneric, t ); *pSyntheticColorMapping = mapSmoothToCubeLinearSat; } else { float t = cfit( flSDRGamutWideness, 0.5f, 1.0f, 0.0f, 1.0f ); *pSynetheticInputColorimetry = wideGamutGeneric; *pSyntheticColorMapping = lerp( mapSmoothToCubeLinearSat, mapPartialToCubeLinearSat, t ); } } bool approxEqual( const glm::vec3 & a, const glm::vec3 & b, float flTolerance = 1e-5f ) { glm::vec3 v = glm::abs(a - b); return ( v.x < flTolerance && v.y < flTolerance && v.z < flTolerance ); } int writeRawLut( const char * outputFilename, uint16_t * pData, size_t nSize ) { FILE *file = fopen( outputFilename, "wb" ); if ( !file ) { return 1; } for ( size_t i=0; i