From a8c6dddd0f4971b17f065358e537f92bc1655352 Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Sat, 3 Aug 2024 08:27:05 +0530 Subject: [PATCH] revisit gainmapmath constants - luminance function for p3 gamut is using weights of DCI P3 instead of Display P3. This is corrected. Accordingly, the gamut conversions involving Display P3 are updated. - Increased precision of some constants used in csc, oetf and eotf functions. - map +inf of hdr intent half fp values to 49.2 instead of zero fixes #177 Test: ./ultrahdr_unit_test Change-Id: Iec77b3d24adb7ad887a8f53d805eecc0c0160f28 --- examples/ultrahdr_app.cpp | 28 +-- lib/include/ultrahdr/gainmapmath.h | 39 +++- lib/include/ultrahdr/ultrahdrcommon.h | 2 + lib/src/dsp/arm/gainmapmath_neon.cpp | 86 ++++++--- lib/src/gainmapmath.cpp | 266 ++++++++++++++++---------- lib/src/icc.cpp | 2 +- lib/src/jpegr.cpp | 37 ++-- tests/gainmapmath_test.cpp | 56 +++--- 8 files changed, 329 insertions(+), 187 deletions(-) diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index 889a55f7..d279a988 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -32,16 +32,22 @@ #include "ultrahdr_api.h" -const float BT601YUVtoRGBMatrix[9] = { - 1, 0, 1.402, 1, (-0.202008 / 0.587), (-0.419198 / 0.587), 1.0, 1.772, 0.0}; +const float DisplayP3YUVtoRGBMatrix[9] = { + 1, 0, 1.542, 1, (-0.146023 / 0.6917), (-0.353118 / 0.6917), 1.0, 1.8414, 0.0}; const float BT709YUVtoRGBMatrix[9] = { 1, 0, 1.5748, 1, (-0.13397432 / 0.7152), (-0.33480248 / 0.7152), 1.0, 1.8556, 0.0}; const float BT2020YUVtoRGBMatrix[9] = { 1, 0, 1.4746, 1, (-0.11156702 / 0.6780), (-0.38737742 / 0.6780), 1, 1.8814, 0}; -const float BT601RGBtoYUVMatrix[9] = { - 0.299, 0.587, 0.114, (-0.299 / 1.772), (-0.587 / 1.772), 0.5, 0.5, (-0.587 / 1.402), - (-0.114 / 1.402)}; +const float DisplayP3RGBtoYUVMatrix[9] = {0.229, + 0.6917, + 0.0793, + (-0.229 / 1.8414), + (-0.6917 / 1.8414), + 0.5, + 0.5, + (-0.6917 / 1.542), + (-0.0793 / 1.542)}; const float BT709RGBtoYUVMatrix[9] = {0.2126, 0.7152, 0.0722, @@ -849,7 +855,7 @@ bool UltraHdrAppInput::convertP010ToRGBImage() { } else if (mHdrCg == UHDR_CG_BT_2100) { coeffs = BT2020YUVtoRGBMatrix; } else if (mHdrCg == UHDR_CG_DISPLAY_P3) { - coeffs = BT601YUVtoRGBMatrix; + coeffs = DisplayP3YUVtoRGBMatrix; } else { std::cerr << "color matrix not present for gamut " << mHdrCg << " using BT2020Matrix" << std::endl; @@ -939,13 +945,13 @@ bool UltraHdrAppInput::convertYuv420ToRGBImage() { uint8_t* u = static_cast(mRawYuv420Image.planes[UHDR_PLANE_U]); uint8_t* v = static_cast(mRawYuv420Image.planes[UHDR_PLANE_V]); - const float* coeffs = BT601YUVtoRGBMatrix; + const float* coeffs = DisplayP3YUVtoRGBMatrix; if (mSdrCg == UHDR_CG_BT_709) { coeffs = BT709YUVtoRGBMatrix; } else if (mSdrCg == UHDR_CG_BT_2100) { coeffs = BT2020YUVtoRGBMatrix; } else if (mSdrCg == UHDR_CG_DISPLAY_P3) { - coeffs = BT601YUVtoRGBMatrix; + coeffs = DisplayP3YUVtoRGBMatrix; } else { std::cerr << "color matrix not present for gamut " << mSdrCg << " using BT601Matrix" << std::endl; @@ -1009,13 +1015,13 @@ bool UltraHdrAppInput::convertRgba8888ToYUV444Image() { uint8_t* uData = static_cast(mDecodedUhdrYuv444Image.planes[UHDR_PLANE_U]); uint8_t* vData = static_cast(mDecodedUhdrYuv444Image.planes[UHDR_PLANE_V]); - const float* coeffs = BT601RGBtoYUVMatrix; + const float* coeffs = DisplayP3RGBtoYUVMatrix; if (mDecodedUhdrRgbImage.cg == UHDR_CG_BT_709) { coeffs = BT709RGBtoYUVMatrix; } else if (mDecodedUhdrRgbImage.cg == UHDR_CG_BT_2100) { coeffs = BT2020RGBtoYUVMatrix; } else if (mDecodedUhdrRgbImage.cg == UHDR_CG_DISPLAY_P3) { - coeffs = BT601RGBtoYUVMatrix; + coeffs = DisplayP3RGBtoYUVMatrix; } else { std::cerr << "color matrix not present for gamut " << mDecodedUhdrRgbImage.cg << " using BT601Matrix" << std::endl; @@ -1063,7 +1069,7 @@ bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() { } else if (mDecodedUhdrRgbImage.cg == UHDR_CG_BT_2100) { coeffs = BT2020RGBtoYUVMatrix; } else if (mDecodedUhdrRgbImage.cg == UHDR_CG_DISPLAY_P3) { - coeffs = BT601RGBtoYUVMatrix; + coeffs = DisplayP3RGBtoYUVMatrix; } else { std::cerr << "color matrix not present for gamut " << mDecodedUhdrRgbImage.cg << " using BT2020Matrix" << std::endl; diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index edaf645c..a8f0b35e 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -347,6 +347,13 @@ Color pqInvOetfLUT(Color e_gamma); constexpr int32_t kPqInvOETFPrecision = 12; constexpr int32_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision; +//////////////////////////////////////////////////////////////////////////////// +// BT.601 transformations + +// BT.601 rgb <-> yuv conversion +Color Bt601RgbToYuv(Color e_gamma); +Color Bt601YuvToRgb(Color e_gamma); + // util class to prepare look up tables for oetf/eotf functions class LookUpTable { public: @@ -404,20 +411,26 @@ Color bt2100ToP3(Color e); // convert between yuv encodings extern const std::array kYuvBt709ToBt601; +extern const std::array kYuvBt709ToDisplayP3; extern const std::array kYuvBt709ToBt2100; -extern const std::array kYuvBt601ToBt709; -extern const std::array kYuvBt601ToBt2100; -extern const std::array kYuvBt2100ToBt709; +extern const std::array kYuvDisplayP3ToBt601; +extern const std::array kYuvDisplayP3ToBt709; +extern const std::array kYuvDisplayP3ToBt2100; extern const std::array kYuvBt2100ToBt601; +extern const std::array kYuvBt2100ToBt709; +extern const std::array kYuvBt2100ToDisplayP3; #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) extern const int16_t kYuv709To601_coeffs_neon[8]; +extern const int16_t kYuv709ToP3_coeffs_neon[8]; extern const int16_t kYuv709To2100_coeffs_neon[8]; -extern const int16_t kYuv601To709_coeffs_neon[8]; -extern const int16_t kYuv601To2100_coeffs_neon[8]; -extern const int16_t kYuv2100To709_coeffs_neon[8]; +extern const int16_t kYuvP3To601_coeffs_neon[8]; +extern const int16_t kYuvP3To709_coeffs_neon[8]; +extern const int16_t kYuvP3To2100_coeffs_neon[8]; extern const int16_t kYuv2100To601_coeffs_neon[8]; +extern const int16_t kYuv2100To709_coeffs_neon[8]; +extern const int16_t kYuv2100ToP3_coeffs_neon[8]; /* * The Y values are provided at half the width of U & V values to allow use of the widening @@ -555,10 +568,18 @@ static inline Color clampPixelFloatLinear(Color e) { return {{{clampPixelFloatLinear(e.r), clampPixelFloatLinear(e.g), clampPixelFloatLinear(e.b)}}}; } +static float mapNonFiniteFloats(float val) { + if (std::isinf(val)) { + return val > 0 ? kMaxPixelFloatHdrLinear : 0.0f; + } + // nan + return 0.0f; +} + static inline Color sanitizePixel(Color e) { - float r = std::isfinite(e.r) ? clampPixelFloatLinear(e.r) : 0.0f; - float g = std::isfinite(e.g) ? clampPixelFloatLinear(e.g) : 0.0f; - float b = std::isfinite(e.b) ? clampPixelFloatLinear(e.b) : 0.0f; + float r = std::isfinite(e.r) ? clampPixelFloatLinear(e.r) : mapNonFiniteFloats(e.r); + float g = std::isfinite(e.g) ? clampPixelFloatLinear(e.g) : mapNonFiniteFloats(e.g); + float b = std::isfinite(e.b) ? clampPixelFloatLinear(e.b) : mapNonFiniteFloats(e.b); return {{{r, g, b}}}; } diff --git a/lib/include/ultrahdr/ultrahdrcommon.h b/lib/include/ultrahdr/ultrahdrcommon.h index 1c146053..28f03386 100644 --- a/lib/include/ultrahdr/ultrahdrcommon.h +++ b/lib/include/ultrahdr/ultrahdrcommon.h @@ -159,6 +159,8 @@ static const uhdr_error_info_t g_no_error = {UHDR_CODEC_OK, 0, ""}; +static const int UHDR_CG_BT_601 = 3; /**< BT.601 */ + namespace ultrahdr { // =============================================================================================== diff --git a/lib/src/dsp/arm/gainmapmath_neon.cpp b/lib/src/dsp/arm/gainmapmath_neon.cpp index b6b879f9..b08091f4 100644 --- a/lib/src/dsp/arm/gainmapmath_neon.cpp +++ b/lib/src/dsp/arm/gainmapmath_neon.cpp @@ -35,12 +35,19 @@ namespace ultrahdr { // {Y1, Y2, U1, U2, V1, V2, 0, 0} // Yuv Bt709 -> Yuv Bt601 -// Y' = (1.0f * Y) + ( 0.101579f * U) + ( 0.196076f * V) -// U' = (0.0f * Y) + ( 0.989854f * U) + (-0.110653f * V) -// V' = (0.0f * Y) + (-0.072453f * U) + ( 0.983398f * V) +// Y' = (1.0 * Y) + ( 0.101579 * U) + ( 0.196076 * V) +// U' = (0.0 * Y) + ( 0.989854 * U) + (-0.110653 * V) +// V' = (0.0 * Y) + (-0.072453 * U) + ( 0.983398 * V) ALIGNED(16) const int16_t kYuv709To601_coeffs_neon[8] = {1664, 3213, 16218, -1813, -1187, 16112, 0, 0}; +// Yuv Bt709 -> Display P3 +// Y' = (1.0 * Y) + ( 0.017545 * U) + ( 0.03677 * V) +// U' = (0.0 * Y) + ( 0.998169 * U) + (-0.019968 * V) +// V' = (0.0 * Y) + (-0.011378 * U) + ( 0.997393 * V) +ALIGNED(16) +const int16_t kYuv709ToP3_coeffs_neon[8] = {287, 602, 16354, -327, -186, 16341, 0, 0}; + // Yuv Bt709 -> Yuv Bt2100 // Y' = (1.0f * Y) + (-0.016969f * U) + ( 0.096312f * V) // U' = (0.0f * Y) + ( 0.995306f * U) + (-0.051192f * V) @@ -48,34 +55,48 @@ const int16_t kYuv709To601_coeffs_neon[8] = {1664, 3213, 16218, -1813, -1187, 16 ALIGNED(16) const int16_t kYuv709To2100_coeffs_neon[8] = {-278, 1578, 16307, -839, 189, 16427, 0, 0}; -// Yuv Bt601 -> Yuv Bt709 -// Y' = (1.0f * Y) + (-0.118188f * U) + (-0.212685f * V), -// U' = (0.0f * Y) + ( 1.018640f * U) + ( 0.114618f * V), -// V' = (0.0f * Y) + ( 0.075049f * U) + ( 1.025327f * V); +// Yuv Display P3 -> Yuv Bt601 +// Y' = (1.0 * Y) + ( 0.086028 * U) + ( 0.161445 * V) +// U' = (0.0 * Y) + ( 0.990631 * U) + (-0.091109 * V) +// V' = (0.0 * Y) + (-0.061361 * U) + ( 0.98474 * V) ALIGNED(16) -const int16_t kYuv601To709_coeffs_neon[8] = {-1936, -3485, 16689, 1878, 1230, 16799, 0, 0}; +const int16_t kYuvP3To601_coeffs_neon[8] = {1409, 2645, 16230, -1493, -1005, 16134, 0, 0}; -// Yuv Bt601 -> Yuv Bt2100 -// Y' = (1.0f * Y) + (-0.128245f * U) + (-0.115879f * V) -// U' = (0.0f * Y) + ( 1.010016f * U) + ( 0.061592f * V) -// V' = (0.0f * Y) + ( 0.086969f * U) + ( 1.029350f * V) +// Yuv Display P3 -> Yuv Bt709 +// Y' = (1.0 * Y) + (-0.018002 * U) + (-0.037226 * V) +// U' = (0.0 * Y) + ( 1.002063 * U) + ( 0.020061 * V) +// V' = (0.0 * Y) + ( 0.011431 * U) + ( 1.002843 * V) ALIGNED(16) -const int16_t kYuv601To2100_coeffs_neon[8] = {-2101, -1899, 16548, 1009, 1425, 16865, 0, 0}; +const int16_t kYuvP3To709_coeffs_neon[8] = {-295, -610, 16418, 329, 187, 16431, 0, 0}; -// Yuv Bt2100 -> Yuv Bt709 -// Y' = (1.0f * Y) + ( 0.018149f * U) + (-0.095132f * V) -// U' = (0.0f * Y) + ( 1.004123f * U) + ( 0.051267f * V) -// V' = (0.0f * Y) + (-0.011524f * U) + ( 0.996782f * V) +// Yuv Display P3 -> Yuv Bt2100 +// Y' = (1.0 * Y) + (-0.033905 * U) + ( 0.059019 * V) +// U' = (0.0 * Y) + ( 0.996774 * U) + ( -0.03137 * V) +// V' = (0.0 * Y) + ( 0.022992 * U) + ( 1.005718 * V) ALIGNED(16) -const int16_t kYuv2100To709_coeffs_neon[8] = {297, -1559, 16452, 840, -189, 16331, 0, 0}; +const int16_t kYuvP3To2100_coeffs_neon[8] = {-555, 967, 16331, -514, 377, 16478, 0, 0}; // Yuv Bt2100 -> Yuv Bt601 -// Y' = (1.0f * Y) + ( 0.117887f * U) + ( 0.105521f * V) -// U' = (0.0f * Y) + ( 0.995211f * U) + (-0.059549f * V) -// V' = (0.0f * Y) + (-0.084085f * U) + ( 0.976518f * V) +// Y' = (1.0 * Y) + ( 0.117887 * U) + ( 0.105521 * V) +// U' = (0.0 * Y) + ( 0.995211 * U) + (-0.059549 * V) +// V' = (0.0 * Y) + (-0.084085 * U) + ( 0.976518 * V) ALIGNED(16) const int16_t kYuv2100To601_coeffs_neon[8] = {1931, 1729, 16306, -976, -1378, 15999, 0, 0}; +// Yuv Bt2100 -> Yuv Bt709 +// Y' = (1.0 * Y) + ( 0.018149 * U) + (-0.095132 * V) +// U' = (0.0 * Y) + ( 1.004123 * U) + ( 0.051267 * V) +// V' = (0.0 * Y) + (-0.011524 * U) + ( 0.996782 * V) +ALIGNED(16) +const int16_t kYuv2100To709_coeffs_neon[8] = {297, -1559, 16452, 840, -189, 16331, 0, 0}; + +// Yuv Bt2100 -> Yuv Display P3 +// Y' = (1.0 * Y) + ( 0.035343 * U) + ( -0.057581 * V) +// U' = (0.0 * Y) + ( 1.002515 * U) + ( 0.03127 * V) +// V' = (0.0 * Y) + (-0.022919 * U) + ( 0.9936 * V) +ALIGNED(16) +const int16_t kYuv2100ToP3_coeffs_neon[8] = {579, -943, 16425, 512, -376, 16279, 0, 0}; + static inline int16x8_t yConversion_neon(uint8x8_t y, int16x8_t u, int16x8_t v, int16x8_t coeffs) { int32x4_t lo = vmull_lane_s16(vget_low_s16(u), vget_low_s16(coeffs), 0); int32x4_t hi = vmull_lane_s16(vget_high_s16(u), vget_low_s16(coeffs), 0); @@ -240,11 +261,14 @@ uhdr_error_info_t convertYuv_neon(uhdr_raw_image_t* image, uhdr_color_gamut_t sr switch (src_encoding) { case UHDR_CG_BT_709: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs = kYuv709To601_coeffs_neon; + break; case UHDR_CG_BT_709: return status; case UHDR_CG_DISPLAY_P3: - coeffs = kYuv709To601_coeffs_neon; + coeffs = kYuv709ToP3_coeffs_neon; break; case UHDR_CG_BT_2100: coeffs = kYuv709To2100_coeffs_neon; @@ -258,14 +282,17 @@ uhdr_error_info_t convertYuv_neon(uhdr_raw_image_t* image, uhdr_color_gamut_t sr } break; case UHDR_CG_DISPLAY_P3: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs = kYuvP3To601_coeffs_neon; + break; case UHDR_CG_BT_709: - coeffs = kYuv601To709_coeffs_neon; + coeffs = kYuvP3To709_coeffs_neon; break; case UHDR_CG_DISPLAY_P3: return status; case UHDR_CG_BT_2100: - coeffs = kYuv601To2100_coeffs_neon; + coeffs = kYuvP3To2100_coeffs_neon; break; default: status.error_code = UHDR_CODEC_INVALID_PARAM; @@ -276,12 +303,15 @@ uhdr_error_info_t convertYuv_neon(uhdr_raw_image_t* image, uhdr_color_gamut_t sr } break; case UHDR_CG_BT_2100: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs = kYuv2100To601_coeffs_neon; + break; case UHDR_CG_BT_709: coeffs = kYuv2100To709_coeffs_neon; break; case UHDR_CG_DISPLAY_P3: - coeffs = kYuv2100To601_coeffs_neon; + coeffs = kYuv2100ToP3_coeffs_neon; break; case UHDR_CG_BT_2100: return status; diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index 202970be..6b7d3cad 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -85,28 +85,89 @@ void ShepardsIDW::fillShepardsIDW(float* weights, int incR, int incB) { } } +//////////////////////////////////////////////////////////////////////////////// +// NOTES: +// +// For Luminance computation from chromaticity coordinates (Rx, Ry, Gx, Gy, Bx, By, Wx, Wy), See +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARY_CONVERSION +// +// a = ((1 - Wx) / Wy) +// b = ((1 - Rx) / Ry) +// c = ((1 - Gx) / Gy) +// d = ((1 - Bx) / By) +// p = Wx / Wy +// q = Rx / Ry +// r = Gx / Gy +// s = Bx / By +// +// BYNum = ((a - b) * (r - q)) - ((p - q) * (c - b)) +// BYDen = ((d - b) * (r - q)) - ((s - q) * (c - b)) +// roundFactor = 7 +// BY = round(BYNum / BYDen, roundFactor) +// GY = round((p - q - BY * (s - q)) / (r - q), roundFactor) +// RY = round(1 - BY - GY, roundFactor) +// +// Luminance of pixel(r, g, b) is, +// Luminance = RY * pixel.r + GY * pixel.g + BY * pixel.b +// +// For RGB to YCbCr conversions for a set of primaries, See +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#MODEL_YUV +// Y' = KR * pixel.r' + (1 - KR - KB) * pixel.g' + KB * pixel.b' +// Cb' = (pixel.b' - Y) / (2 * (1 - KB)) +// Cr' = (pixel.r' - Y) / (2 * (1 - KR)) +// Here KR and KB are computed as per equations (39) - (44) of ITU H.273. +// Strangely, RY = KR, GY = (1 - KR - KB), BY = KB. This cannot be a coincidence !!! +// Y' was meant to approximate a perceptually uniform correlate of luminance. Hence the same +// weights? +// +// For YCbCr to RGB conversions for a set of primaries, See +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#MODEL_YUV +// pixel.r' = Y' + ((2 * (1 - KR)) * Cr') +// pixel.g' = Y' - ((2 * (KR * (1 - KR) * Cr' + KB * (1 - KB) * Cb')) / (1 - KR - KB)) +// pixel.b' = Y' + ((2 * (1 - KB)) * Cb') +// +// Addl. References: +// +// sRGB and BT709 share same chromaticity coordinates +// BT709 (0.640, 0.330, 0.300, 0.600, 0.150, 0.060, 0.3127, 0.3290) +// BT709 Luminance +// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARIES_BT709 +// BT709 RGB <-> YCbCr +// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#MODEL_BT709 +// sRGB EOTF, EOTF Inv +// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_SRGB +// +// DisplayP3 (0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290) +// DisplayP3 Luminance +// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARIES_DISPLAYP3 +// +// BT2100 (0.708, 0.292, 0.170, 0.797, 0.131, 0.046, 0.3127, 0.3290) +// BT2100 Luminance +// See, https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARIES_BT2020 +// BT2100 RGB <-> YCbCr +// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#MODEL_BT2020 +// HLG OETF, Inverse OETF, OOTF and Inverse OOTF +// See, https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_HLG +// PQ EOTF Inverse EOTF +// See, https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_PQ +// + //////////////////////////////////////////////////////////////////////////////// // sRGB transformations // See IEC 61966-2-1/Amd 1:2003, Equation F.7. -static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f; +static const float kSrgbR = 0.212639f, kSrgbG = 0.715169f, kSrgbB = 0.072192f; float srgbLuminance(Color e) { return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; } // See ITU-R BT.709-6, Section 3. -// Uses the same coefficients for deriving luma signal as -// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance -// function above. -static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f; +static const float kSrgbCb = (2 * (1 - kSrgbB)), kSrgbCr = (2 * (1 - kSrgbR)); Color srgbRgbToYuv(Color e_gamma) { float y_gamma = srgbLuminance(e_gamma); return {{{y_gamma, (e_gamma.b - y_gamma) / kSrgbCb, (e_gamma.r - y_gamma) / kSrgbCr}}}; } -// See ITU-R BT.709-6, Section 3. -// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we -// can reuse the luminance coefficients since they are the same. static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG; static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG; @@ -121,7 +182,7 @@ float srgbInvOetf(float e_gamma) { if (e_gamma <= 0.04045f) { return e_gamma / 12.92f; } else { - return pow((e_gamma + 0.055f) / 1.055f, 2.4); + return pow((e_gamma + 0.055f) / 1.055f, 2.4f); } } @@ -129,7 +190,6 @@ Color srgbInvOetf(Color e_gamma) { return {{{srgbInvOetf(e_gamma.r), srgbInvOetf(e_gamma.g), srgbInvOetf(e_gamma.b)}}}; } -// See IEC 61966-2-1, Equations F.5 and F.6. float srgbInvOetfLUT(float e_gamma) { int32_t value = static_cast(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5); // TODO() : Remove once conversion modules have appropriate clamping in place @@ -142,15 +202,16 @@ Color srgbInvOetfLUT(Color e_gamma) { return {{{srgbInvOetfLUT(e_gamma.r), srgbInvOetfLUT(e_gamma.g), srgbInvOetfLUT(e_gamma.b)}}}; } +// See IEC 61966-2-1/Amd 1:2003, Equations F.10 and F.11. float srgbOetf(float e) { - constexpr float kThreshold = 0.0031308; - constexpr float kLowSlope = 12.92; - constexpr float kHighOffset = 0.055; - constexpr float kPowerExponent = 1.0 / 2.4; + constexpr float kThreshold = 0.0031308f; + constexpr float kLowSlope = 12.92f; + constexpr float kHighOffset = 0.055f; + constexpr float kPowerExponent = 1.0f / 2.4f; if (e <= kThreshold) { return kLowSlope * e; } - return (1.0 + kHighOffset) * std::pow(e, kPowerExponent) - kHighOffset; + return (1.0f + kHighOffset) * std::pow(e, kPowerExponent) - kHighOffset; } Color srgbOetf(Color e) { return {{{srgbOetf(e.r), srgbOetf(e.g), srgbOetf(e.b)}}}; } @@ -158,27 +219,20 @@ Color srgbOetf(Color e) { return {{{srgbOetf(e.r), srgbOetf(e.g), srgbOetf(e.b)} //////////////////////////////////////////////////////////////////////////////// // Display-P3 transformations -// See SMPTE EG 432-1, Equation 7-8. -static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f; +// See SMPTE EG 432-1, Equation G-7. +static const float kP3R = 0.2289746f, kP3G = 0.6917385f, kP3B = 0.0792869f; float p3Luminance(Color e) { return kP3R * e.r + kP3G * e.g + kP3B * e.b; } -// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. -// Unfortunately, calculation of luma signal differs from calculation of -// luminance for Display-P3, so we can't reuse p3Luminance here. -static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f; -static const float kP3Cb = 1.772f, kP3Cr = 1.402f; +static const float kP3Cb = (2 * (1 - kP3B)), kP3Cr = (2 * (1 - kP3R)); Color p3RgbToYuv(Color e_gamma) { - float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b; + float y_gamma = p3Luminance(e_gamma); return {{{y_gamma, (e_gamma.b - y_gamma) / kP3Cb, (e_gamma.r - y_gamma) / kP3Cr}}}; } -// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. -// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must -// use luma signal coefficients rather than the luminance coefficients. -static const float kP3GCb = kP3YB * kP3Cb / kP3YG; -static const float kP3GCr = kP3YR * kP3Cr / kP3YG; +static const float kP3GCb = kP3B * kP3Cb / kP3G; +static const float kP3GCr = kP3R * kP3Cr / kP3G; Color p3YuvToRgb(Color e_gamma) { return {{{clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v), @@ -190,45 +244,18 @@ Color p3YuvToRgb(Color e_gamma) { // BT.2100 transformations - according to ITU-R BT.2100-2 // See ITU-R BT.2100-2, Table 5, HLG Reference OOTF -static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; +static const float kBt2100R = 0.2627f, kBt2100G = 0.677998f, kBt2100B = 0.059302f; float bt2100Luminance(Color e) { return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; } // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. -// BT.2100 uses the same coefficients for calculating luma signal and luminance, -// so we reuse the luminance function here. -static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; +static const float kBt2100Cb = (2 * (1 - kBt2100B)), kBt2100Cr = (2 * (1 - kBt2100R)); Color bt2100RgbToYuv(Color e_gamma) { float y_gamma = bt2100Luminance(e_gamma); return {{{y_gamma, (e_gamma.b - y_gamma) / kBt2100Cb, (e_gamma.r - y_gamma) / kBt2100Cr}}}; } -// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. -// -// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients. -// -// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty -// straight forward; we just invert the formulas for U and V above. But deriving -// the formula for G is a bit more complicated: -// -// Start with equation for luminance: -// Y = kBt2100R * R + kBt2100G * G + kBt2100B * B -// Solve for G: -// G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B -// Substitute equations for R and B in terms YUV: -// G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B -// Simplify: -// G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G) -// + U * (kBt2100B * kBt2100Cb / kBt2100G) -// + V * (kBt2100R * kBt2100Cr / kBt2100G) -// -// We then get the following coeficients for calculating G from YUV: -// -// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1 -// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645 -// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713 - static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G; static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G; @@ -239,7 +266,7 @@ Color bt2100YuvToRgb(Color e_gamma) { } // See ITU-R BT.2100-2, Table 5, HLG Reference OETF. -static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; +static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073f; float hlgOetf(float e) { if (e <= 1.0f / 12.0f) { @@ -286,9 +313,11 @@ Color hlgInvOetfLUT(Color e_gamma) { return {{{hlgInvOetfLUT(e_gamma.r), hlgInvOetfLUT(e_gamma.g), hlgInvOetfLUT(e_gamma.b)}}}; } -// 1.2f + 0.42 * log(kHlgMaxNits / 1000) +// See ITU-R BT.2100-2, Table 5, Note 5f +// Gamma = 1.2 + 0.42 * log(kHlgMaxNits / 1000) static const float kOotfGamma = 1.2f; +// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF Color hlgOotf(Color e, LuminanceFn luminance) { float y = luminance(e); return e * std::pow(y, kOotfGamma - 1.0f); @@ -298,6 +327,7 @@ Color hlgOotfApprox(Color e, [[maybe_unused]] LuminanceFn luminance) { return {{{std::pow(e.r, kOotfGamma), std::pow(e.g, kOotfGamma), std::pow(e.b, kOotfGamma)}}}; } +// See ITU-R BT.2100-2, Table 5, Note 5i Color hlgInverseOotf(Color e, LuminanceFn luminance) { float y = luminance(e); return e * std::pow(y, (1.0f / kOotfGamma) - 1.0f); @@ -351,6 +381,26 @@ Color pqInvOetfLUT(Color e_gamma) { return {{{pqInvOetfLUT(e_gamma.r), pqInvOetfLUT(e_gamma.g), pqInvOetfLUT(e_gamma.b)}}}; } +//////////////////////////////////////////////////////////////////////////////// +// BT.601 transformations + +// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. +static const float kBt601R = 0.299f, kBt601G = 0.587f, kBt601B = 0.114f; +static const float kBt601Cb = (2 * (1 - kBt601B)), kBt601Cr = (2 * (1 - kBt601R)); +static const float kBt601GCb = kBt601B * kBt601Cb / kBt601G; +static const float kBt601GCr = kBt601R * kBt601Cr / kBt601G; + +Color Bt601RgbToYuv(Color e_gamma) { + float y_gamma = kBt601R * e_gamma.r + kBt601G * e_gamma.g + kBt601B * e_gamma.b; + return {{{y_gamma, (e_gamma.b - y_gamma) / kBt601Cb, (e_gamma.r - y_gamma) / kBt601Cr}}}; +} + +Color Bt601YuvToRgb(Color e_gamma) { + return {{{clampPixelFloat(e_gamma.y + kBt601Cr * e_gamma.v), + clampPixelFloat(e_gamma.y - kBt601GCb * e_gamma.u - kBt601GCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kBt601Cb * e_gamma.u)}}}; +} + //////////////////////////////////////////////////////////////////////////////// // Color access functions @@ -555,47 +605,48 @@ void putYuv444Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) { //////////////////////////////////////////////////////////////////////////////// // Color space conversions +// Sample, See, +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#_bt_709_bt_2020_primary_conversion_example Color bt709ToP3(Color e) { - return {{{clampPixelFloat(0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b), - clampPixelFloat(0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b), - clampPixelFloat(0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b)}}}; + return {{{clampPixelFloat(0.822462f * e.r + 0.177537f * e.g + 0.000001f * e.b), + clampPixelFloat(0.033194f * e.r + 0.966807f * e.g + -0.000001f * e.b), + clampPixelFloat(0.017083f * e.r + 0.072398f * e.g + 0.91052f * e.b)}}}; } Color bt709ToBt2100(Color e) { - return {{{clampPixelFloat(0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b), - clampPixelFloat(0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b), - clampPixelFloat(0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b)}}}; + return {{{clampPixelFloat(0.627404f * e.r + 0.329282f * e.g + 0.043314f * e.b), + clampPixelFloat(0.069097f * e.r + 0.919541f * e.g + 0.011362f * e.b), + clampPixelFloat(0.016392f * e.r + 0.088013f * e.g + 0.895595f * e.b)}}}; } Color p3ToBt709(Color e) { - return {{{clampPixelFloat(1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b), - clampPixelFloat(-0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b), - clampPixelFloat(-0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b)}}}; + return {{{clampPixelFloat(1.22494f * e.r + -0.22494f * e.g + 0.0f * e.b), + clampPixelFloat(-0.042057f * e.r + 1.042057f * e.g + 0.0f * e.b), + clampPixelFloat(-0.019638f * e.r + -0.078636f * e.g + 1.098274f * e.b)}}}; } Color p3ToBt2100(Color e) { - return {{{clampPixelFloat(0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b), - clampPixelFloat(0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b), - clampPixelFloat(-0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b)}}}; + return {{{clampPixelFloat(0.753833f * e.r + 0.198597f * e.g + 0.04757f * e.b), + clampPixelFloat(0.045744f * e.r + 0.941777f * e.g + 0.012479f * e.b), + clampPixelFloat(-0.00121f * e.r + 0.017601f * e.g + 0.983608f * e.b)}}}; } Color bt2100ToBt709(Color e) { - return {{{clampPixelFloat(1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b), - clampPixelFloat(-0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b), - clampPixelFloat(-0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b)}}}; + return {{{clampPixelFloat(1.660491f * e.r + -0.587641f * e.g + -0.07285f * e.b), + clampPixelFloat(-0.124551f * e.r + 1.1329f * e.g + -0.008349f * e.b), + clampPixelFloat(-0.018151f * e.r + -0.100579f * e.g + 1.11873f * e.b)}}}; } Color bt2100ToP3(Color e) { - return {{{clampPixelFloat(1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b), - clampPixelFloat(-0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b), - clampPixelFloat(0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b)}}}; + return {{{clampPixelFloat(1.343578f * e.r + -0.282179f * e.g + -0.061399f * e.b), + clampPixelFloat(-0.065298f * e.r + 1.075788f * e.g + -0.01049f * e.b), + clampPixelFloat(0.002822f * e.r + -0.019598f * e.g + 1.016777f * e.b)}}}; } // All of these conversions are derived from the respective input YUV->RGB conversion followed by // the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in -// gainmapmath.cpp, given that we use BT.709 encoding for sRGB and BT.601 encoding for Display-P3, -// to match DataSpace. +// gainmapmath.cpp. // Yuv Bt709 -> Yuv Bt601 // Y' = (1.0 * Y) + ( 0.101579 * U) + ( 0.196076 * V) @@ -604,6 +655,13 @@ Color bt2100ToP3(Color e) { const std::array kYuvBt709ToBt601 = { 1.0f, 0.101579f, 0.196076f, 0.0f, 0.989854f, -0.110653f, 0.0f, -0.072453f, 0.983398f}; +// Yuv Bt709 -> Display P3 +// Y' = (1.0 * Y) + ( 0.017545 * U) + ( 0.03677 * V) +// U' = (0.0 * Y) + ( 0.998169 * U) + (-0.019968 * V) +// V' = (0.0 * Y) + (-0.011378 * U) + ( 0.997393 * V) +const std::array kYuvBt709ToDisplayP3 = { + 1.0f, 0.017545f, 0.03677f, 0.0f, 0.998169f, -0.019968f, 0.0f, -0.011378f, 0.997393f}; + // Yuv Bt709 -> Yuv Bt2100 // Y' = (1.0 * Y) + (-0.016969 * U) + ( 0.096312 * V) // U' = (0.0 * Y) + ( 0.995306 * U) + (-0.051192 * V) @@ -611,19 +669,33 @@ const std::array kYuvBt709ToBt601 = { const std::array kYuvBt709ToBt2100 = { 1.0f, -0.016969f, 0.096312f, 0.0f, 0.995306f, -0.051192f, 0.0f, 0.011507f, 1.002637f}; -// Yuv Bt601 -> Yuv Bt709 -// Y' = (1.0 * Y) + (-0.118188 * U) + (-0.212685 * V) -// U' = (0.0 * Y) + ( 1.018640 * U) + ( 0.114618 * V) -// V' = (0.0 * Y) + ( 0.075049 * U) + ( 1.025327 * V) -const std::array kYuvBt601ToBt709 = { - 1.0f, -0.118188f, -0.212685f, 0.0f, 1.018640f, 0.114618f, 0.0f, 0.075049f, 1.025327f}; +// Display P3 -> Yuv Bt601 +// Y' = (1.0 * Y) + ( 0.086028 * U) + ( 0.161445 * V) +// U' = (0.0 * Y) + ( 0.990631 * U) + (-0.091109 * V) +// V' = (0.0 * Y) + (-0.061361 * U) + ( 0.98474 * V) +const std::array kYuvDisplayP3ToBt601 = { + 1.0f, 0.086028f, 0.161445f, 0.0f, 0.990631f, -0.091109f, 0.0f, -0.061361f, 0.98474f}; + +// Display P3 -> Yuv Bt709 +// Y' = (1.0 * Y) + (-0.018002 * U) + (-0.037226 * V) +// U' = (0.0 * Y) + ( 1.002063 * U) + ( 0.020061 * V) +// V' = (0.0 * Y) + ( 0.011431 * U) + ( 1.002843 * V) +const std::array kYuvDisplayP3ToBt709 = { + 1.0f, -0.018002f, -0.037226f, 0.0f, 1.002063f, 0.020061f, 0.0f, 0.011431f, 1.002843f}; + +// Display P3 -> Yuv Bt2100 +// Y' = (1.0 * Y) + (-0.033905 * U) + ( 0.059019 * V) +// U' = (0.0 * Y) + ( 0.996774 * U) + ( -0.03137 * V) +// V' = (0.0 * Y) + ( 0.022992 * U) + ( 1.005718 * V) +const std::array kYuvDisplayP3ToBt2100 = { + 1.0f, -0.033905f, 0.059019f, 0.0f, 0.996774f, -0.03137f, 0.0f, 0.022992f, 1.005718f}; -// Yuv Bt601 -> Yuv Bt2100 -// Y' = (1.0 * Y) + (-0.128245 * U) + (-0.115879 * V) -// U' = (0.0 * Y) + ( 1.010016 * U) + ( 0.061592 * V) -// V' = (0.0 * Y) + ( 0.086969 * U) + ( 1.029350 * V) -const std::array kYuvBt601ToBt2100 = { - 1.0f, -0.128245f, -0.115879, 0.0f, 1.010016f, 0.061592f, 0.0f, 0.086969f, 1.029350f}; +// Yuv Bt2100 -> Yuv Bt601 +// Y' = (1.0 * Y) + ( 0.117887 * U) + ( 0.105521 * V) +// U' = (0.0 * Y) + ( 0.995211 * U) + (-0.059549 * V) +// V' = (0.0 * Y) + (-0.084085 * U) + ( 0.976518 * V) +const std::array kYuvBt2100ToBt601 = { + 1.0f, 0.117887f, 0.105521f, 0.0f, 0.995211f, -0.059549f, 0.0f, -0.084085f, 0.976518f}; // Yuv Bt2100 -> Yuv Bt709 // Y' = (1.0 * Y) + ( 0.018149 * U) + (-0.095132 * V) @@ -632,12 +704,12 @@ const std::array kYuvBt601ToBt2100 = { const std::array kYuvBt2100ToBt709 = { 1.0f, 0.018149f, -0.095132f, 0.0f, 1.004123f, 0.051267f, 0.0f, -0.011524f, 0.996782f}; -// Yuv Bt2100 -> Yuv Bt601 -// Y' = (1.0 * Y) + ( 0.117887 * U) + ( 0.105521 * V) -// U' = (0.0 * Y) + ( 0.995211 * U) + (-0.059549 * V) -// V' = (0.0 * Y) + (-0.084085 * U) + ( 0.976518 * V) -const std::array kYuvBt2100ToBt601 = { - 1.0f, 0.117887f, 0.105521f, 0.0f, 0.995211f, -0.059549f, 0.0f, -0.084085f, 0.976518f}; +// Yuv Bt2100 -> Display P3 +// Y' = (1.0 * Y) + ( 0.035343 * U) + ( -0.057581 * V) +// U' = (0.0 * Y) + ( 1.002515 * U) + ( 0.03127 * V) +// V' = (0.0 * Y) + (-0.022919 * U) + ( 0.9936 * V) +const std::array kYuvBt2100ToDisplayP3 = { + 1.0f, 0.035343f, -0.057581f, 0.0f, 1.002515f, 0.03127f, 0.0f, -0.022919f, 0.9936f}; Color yuvColorGamutConversion(Color e_gamma, const std::array& coeffs) { const float y = e_gamma.y * std::get<0>(coeffs) + e_gamma.u * std::get<1>(coeffs) + diff --git a/lib/src/icc.cpp b/lib/src/icc.cpp index b4fd11cd..0e43488a 100644 --- a/lib/src/icc.cpp +++ b/lib/src/icc.cpp @@ -602,7 +602,7 @@ std::shared_ptr IccHelper::writeIccProfile(uhdr_color_transfer_t tf, bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_tag, const uint8_t* green_tag, const uint8_t* blue_tag) { - const float tolerance = 0.001; + const float tolerance = 0.001f; Fixed r_x_fixed = Endian_SwapBE32(reinterpret_cast(const_cast(red_tag))[2]); Fixed r_y_fixed = Endian_SwapBE32(reinterpret_cast(const_cast(red_tag))[3]); Fixed r_z_fixed = Endian_SwapBE32(reinterpret_cast(const_cast(red_tag))[4]); diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 0043526f..0ee53813 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -271,9 +271,11 @@ uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_imag // convert to bt601 YUV encoding for JPEG encode #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) - UHDR_ERR_CHECK(convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3)); + UHDR_ERR_CHECK( + convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); #else - UHDR_ERR_CHECK(convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3)); + UHDR_ERR_CHECK( + convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); #endif // compress sdr image @@ -423,11 +425,14 @@ uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t switch (src_encoding) { case UHDR_CG_BT_709: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs_ptr = &kYuvBt709ToBt601; + break; case UHDR_CG_BT_709: return status; case UHDR_CG_DISPLAY_P3: - coeffs_ptr = &kYuvBt709ToBt601; + coeffs_ptr = &kYuvBt709ToDisplayP3; break; case UHDR_CG_BT_2100: coeffs_ptr = &kYuvBt709ToBt2100; @@ -441,14 +446,17 @@ uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t } break; case UHDR_CG_DISPLAY_P3: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs_ptr = &kYuvDisplayP3ToBt601; + break; case UHDR_CG_BT_709: - coeffs_ptr = &kYuvBt601ToBt709; + coeffs_ptr = &kYuvDisplayP3ToBt709; break; case UHDR_CG_DISPLAY_P3: return status; case UHDR_CG_BT_2100: - coeffs_ptr = &kYuvBt601ToBt2100; + coeffs_ptr = &kYuvDisplayP3ToBt2100; break; default: status.error_code = UHDR_CODEC_INVALID_PARAM; @@ -459,12 +467,15 @@ uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t } break; case UHDR_CG_BT_2100: - switch (dst_encoding) { + switch ((int)dst_encoding) { + case UHDR_CG_BT_601: + coeffs_ptr = &kYuvBt2100ToBt601; + break; case UHDR_CG_BT_709: coeffs_ptr = &kYuvBt2100ToBt709; break; case UHDR_CG_DISPLAY_P3: - coeffs_ptr = &kYuvBt2100ToBt601; + coeffs_ptr = &kYuvBt2100ToDisplayP3; break; case UHDR_CG_BT_2100: return status; @@ -920,7 +931,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ min_content_boost_log2 = (std::max)(min_content_boost_log2, suggestion); } if (fabs(max_content_boost_log2 - min_content_boost_log2) < FLT_EPSILON) { - max_content_boost_log2 += 0.1; // to avoid div by zero during affine transform + max_content_boost_log2 += 0.1f; // to avoid div by zero during affine transform } std::function encodeMap = [this, gainmap_data, map_width, dest, min_content_boost_log2, @@ -1431,7 +1442,7 @@ uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ima float gainmap_aspect_ratio = (float)gainmap_img->w / gainmap_img->h; float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio); // Allow 1% delta - const float delta_tolerance = 0.01; + const float delta_tolerance = 0.01f; if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; @@ -1704,8 +1715,8 @@ uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_in } static float ReinhardMap(float y_hdr, float headroom) { - float out = 1.0 + y_hdr / (headroom * headroom); - out /= 1.0 + y_hdr; + float out = 1.0f + y_hdr / (headroom * headroom); + out /= 1.0f + y_hdr; return out * y_hdr; } diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp index addc81fb..4d381bc9 100644 --- a/tests/gainmapmath_test.cpp +++ b/tests/gainmapmath_test.cpp @@ -91,9 +91,9 @@ class GainMapMathTest : public testing::Test { Color SrgbYuvGreen() { return {{{0.7152f, -0.38543f, -0.45415f}}}; } Color SrgbYuvBlue() { return {{{0.0722f, 0.5f, -0.04585f}}}; } - Color P3YuvRed() { return {{{0.299f, -0.16874f, 0.5f}}}; } - Color P3YuvGreen() { return {{{0.587f, -0.33126f, -0.41869f}}}; } - Color P3YuvBlue() { return {{{0.114f, 0.5f, -0.08131f}}}; } + Color P3YuvRed() { return {{{0.229f, -0.124362f, 0.5f}}}; } + Color P3YuvGreen() { return {{{0.6917f, -0.375638f, -0.448573f}}}; } + Color P3YuvBlue() { return {{{0.0793f, 0.5f, -0.051427f}}}; } Color Bt2100YuvRed() { return {{{0.2627f, -0.13963f, 0.5f}}}; } Color Bt2100YuvGreen() { return {{{0.6780f, -0.36037f, -0.45979f}}}; } @@ -116,9 +116,9 @@ class GainMapMathTest : public testing::Test { Pixel SrgbYuvGreenPixel() { return {182, -98, -116}; } Pixel SrgbYuvBluePixel() { return {18, 128, -12}; } - Pixel P3YuvRedPixel() { return {76, -43, 128}; } - Pixel P3YuvGreenPixel() { return {150, -84, -107}; } - Pixel P3YuvBluePixel() { return {29, 128, -21}; } + Pixel P3YuvRedPixel() { return {58, -32, 128}; } + Pixel P3YuvGreenPixel() { return {176, -96, -114}; } + Pixel P3YuvBluePixel() { return {20, 128, -13}; } Pixel Bt2100YuvRedPixel() { return {67, -36, 128}; } Pixel Bt2100YuvGreenPixel() { return {173, -92, -117}; } @@ -540,9 +540,9 @@ TEST_F(GainMapMathTest, ColorDivideFloat) { TEST_F(GainMapMathTest, SrgbLuminance) { EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.212639f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.715169f); + EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.072192f); } TEST_F(GainMapMathTest, SrgbYuvToRgb) { @@ -607,9 +607,9 @@ TEST_F(GainMapMathTest, SrgbTransferFunction) { TEST_F(GainMapMathTest, P3Luminance) { EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f); - EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f); - EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f); - EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f); + EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.2289746f); + EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.6917385f); + EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.0792869f); } TEST_F(GainMapMathTest, P3YuvToRgb) { @@ -666,8 +666,8 @@ TEST_F(GainMapMathTest, Bt2100Luminance) { EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f); EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f); EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.677998f); + EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.059302f); } TEST_F(GainMapMathTest, Bt2100YuvToRgb) { @@ -740,12 +740,12 @@ TEST_F(GainMapMathTest, YuvColorGamutConversion) { const std::array>, 6> coeffs_setup_expected{{ - {kYuvBt709ToBt601, SrgbYuvColors, P3YuvColors}, + {kYuvBt709ToDisplayP3, SrgbYuvColors, P3YuvColors}, {kYuvBt709ToBt2100, SrgbYuvColors, Bt2100YuvColors}, - {kYuvBt601ToBt709, P3YuvColors, SrgbYuvColors}, - {kYuvBt601ToBt2100, P3YuvColors, Bt2100YuvColors}, + {kYuvDisplayP3ToBt709, P3YuvColors, SrgbYuvColors}, + {kYuvDisplayP3ToBt2100, P3YuvColors, Bt2100YuvColors}, {kYuvBt2100ToBt709, Bt2100YuvColors, SrgbYuvColors}, - {kYuvBt2100ToBt601, Bt2100YuvColors, P3YuvColors}, + {kYuvBt2100ToDisplayP3, Bt2100YuvColors, P3YuvColors}, }}; for (const auto& [coeffs, input, expected] : coeffs_setup_expected) { @@ -788,12 +788,12 @@ TEST_F(GainMapMathTest, YuvConversionNeon) { const std::array< std::tuple, const std::array>, 6> coeffs_setup_correct{{ - {kYuv709To601_coeffs_neon, SrgbYuvColors, P3YuvColors}, + {kYuv709ToP3_coeffs_neon, SrgbYuvColors, P3YuvColors}, {kYuv709To2100_coeffs_neon, SrgbYuvColors, Bt2100YuvColors}, - {kYuv601To709_coeffs_neon, P3YuvColors, SrgbYuvColors}, - {kYuv601To2100_coeffs_neon, P3YuvColors, Bt2100YuvColors}, + {kYuvP3To709_coeffs_neon, P3YuvColors, SrgbYuvColors}, + {kYuvP3To2100_coeffs_neon, P3YuvColors, Bt2100YuvColors}, {kYuv2100To709_coeffs_neon, Bt2100YuvColors, SrgbYuvColors}, - {kYuv2100To601_coeffs_neon, Bt2100YuvColors, P3YuvColors}, + {kYuv2100ToP3_coeffs_neon, Bt2100YuvColors, P3YuvColors}, }}; for (const auto& [coeff_ptr, input, expected] : coeffs_setup_correct) { @@ -889,8 +889,8 @@ TEST_F(GainMapMathTest, TransformYuv420) { uint8_t* cr = cb + input.w * input.h / 4; const std::array, 6> conversion_coeffs = { - kYuvBt709ToBt601, kYuvBt709ToBt2100, kYuvBt601ToBt709, - kYuvBt601ToBt2100, kYuvBt2100ToBt709, kYuvBt2100ToBt601}; + kYuvBt709ToDisplayP3, kYuvBt709ToBt2100, kYuvDisplayP3ToBt709, + kYuvDisplayP3ToBt2100, kYuvBt2100ToBt709, kYuvBt2100ToDisplayP3}; for (size_t coeffs_idx = 0; coeffs_idx < conversion_coeffs.size(); ++coeffs_idx) { auto output = Yuv420Image(); @@ -958,12 +958,12 @@ TEST_F(GainMapMathTest, TransformYuv420) { #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) TEST_F(GainMapMathTest, TransformYuv420Neon) { const std::array>, 6> fixed_floating_coeffs{ - {{kYuv709To601_coeffs_neon, kYuvBt709ToBt601}, + {{kYuv709ToP3_coeffs_neon, kYuvBt709ToDisplayP3}, {kYuv709To2100_coeffs_neon, kYuvBt709ToBt2100}, - {kYuv601To709_coeffs_neon, kYuvBt601ToBt709}, - {kYuv601To2100_coeffs_neon, kYuvBt601ToBt2100}, + {kYuvP3To709_coeffs_neon, kYuvDisplayP3ToBt709}, + {kYuvP3To2100_coeffs_neon, kYuvDisplayP3ToBt2100}, {kYuv2100To709_coeffs_neon, kYuvBt2100ToBt709}, - {kYuv2100To601_coeffs_neon, kYuvBt2100ToBt601}}}; + {kYuv2100ToP3_coeffs_neon, kYuvBt2100ToDisplayP3}}}; for (const auto& [neon_coeffs_ptr, floating_point_coeffs] : fixed_floating_coeffs) { uhdr_raw_image_t input = Yuv420Image32x4();