/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include namespace android::ultrahdr { class GainMapMathTest : public testing::Test { public: GainMapMathTest(); ~GainMapMathTest(); float ComparisonEpsilon() { return 1e-4f; } float LuminanceEpsilon() { return 1e-2f; } float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); } Color Yuv420(uint8_t y, uint8_t u, uint8_t v) { return {{{ static_cast(y) / 255.0f, (static_cast(u) - 128.0f) / 255.0f, (static_cast(v) - 128.0f) / 255.0f }}}; } Color P010(uint16_t y, uint16_t u, uint16_t v) { return {{{ (static_cast(y) - 64.0f) / 876.0f, (static_cast(u) - 64.0f) / 896.0f - 0.5f, (static_cast(v) - 64.0f) / 896.0f - 0.5f }}}; } float Map(uint8_t e) { return static_cast(e) / 255.0f; } Color ColorMin(Color e1, Color e2) { return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}}; } Color ColorMax(Color e1, Color e2) { return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}}; } Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; } Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; } Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; } Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; } Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; } Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; } 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 Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; } Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; } Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; } float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); float luminance_scaled = luminanceFn(rgb); return luminance_scaled * kSdrWhiteNits; } float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { Color rgb_gamma = p3YuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); float luminance_scaled = luminanceFn(rgb); return luminance_scaled * kSdrWhiteNits; } float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf, ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn, float scale_factor) { Color rgb_gamma = bt2100YuvToRgb(yuv_gamma); Color rgb = hdrInvOetf(rgb_gamma); rgb = gamutConversionFn(rgb); float luminance_scaled = luminanceFn(rgb); return luminance_scaled * scale_factor; } Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); return applyGain(rgb, gain, metadata); } jpegr_uncompressed_struct Yuv420Image() { static uint8_t pixels[] = { // Y 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, // U 0xA0, 0xA1, 0xA2, 0xA3, // V 0xB0, 0xB1, 0xB2, 0xB3, }; return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 }; } Color (*Yuv420Colors())[4] { static Color colors[4][4] = { { Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0), Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1), }, { Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0), Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1), }, { Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2), Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3), }, { Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2), Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3), }, }; return colors; } jpegr_uncompressed_struct P010Image() { static uint16_t pixels[] = { // Y 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6, 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6, 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6, 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6, // UV 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6, 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6, }; return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709 }; } Color (*P010Colors())[4] { static Color colors[4][4] = { { P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0), P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1), }, { P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0), P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1), }, { P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2), P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3), }, { P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2), P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3), }, }; return colors; } jpegr_uncompressed_struct MapImage() { static uint8_t pixels[] = { 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, }; return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED }; } float (*MapValues())[4] { static float values[4][4] = { { Map(0x00), Map(0x10), Map(0x20), Map(0x30), }, { Map(0x01), Map(0x11), Map(0x21), Map(0x31), }, { Map(0x02), Map(0x12), Map(0x22), Map(0x32), }, { Map(0x03), Map(0x13), Map(0x23), Map(0x33), }, }; return values; } protected: virtual void SetUp(); virtual void TearDown(); }; GainMapMathTest::GainMapMathTest() {} GainMapMathTest::~GainMapMathTest() {} void GainMapMathTest::SetUp() {} void GainMapMathTest::TearDown() {} #define EXPECT_RGB_EQ(e1, e2) \ EXPECT_FLOAT_EQ((e1).r, (e2).r); \ EXPECT_FLOAT_EQ((e1).g, (e2).g); \ EXPECT_FLOAT_EQ((e1).b, (e2).b) #define EXPECT_RGB_NEAR(e1, e2) \ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon()) #define EXPECT_RGB_CLOSE(e1, e2) \ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f) #define EXPECT_YUV_EQ(e1, e2) \ EXPECT_FLOAT_EQ((e1).y, (e2).y); \ EXPECT_FLOAT_EQ((e1).u, (e2).u); \ EXPECT_FLOAT_EQ((e1).v, (e2).v) #define EXPECT_YUV_NEAR(e1, e2) \ EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \ EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \ EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon()) #define EXPECT_YUV_BETWEEN(e, min, max) \ EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \ EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \ EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v))) // TODO: a bunch of these tests can be parameterized. TEST_F(GainMapMathTest, ColorConstruct) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; EXPECT_FLOAT_EQ(e1.r, 0.1f); EXPECT_FLOAT_EQ(e1.g, 0.2f); EXPECT_FLOAT_EQ(e1.b, 0.3f); EXPECT_FLOAT_EQ(e1.y, 0.1f); EXPECT_FLOAT_EQ(e1.u, 0.2f); EXPECT_FLOAT_EQ(e1.v, 0.3f); } TEST_F(GainMapMathTest, ColorAddColor) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 + e1; EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); e2 += e1; EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f); EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f); EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f); } TEST_F(GainMapMathTest, ColorAddFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 + 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f); EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f); EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f); e2 += 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f); EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f); EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f); } TEST_F(GainMapMathTest, ColorSubtractColor) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 - e1; EXPECT_FLOAT_EQ(e2.r, 0.0f); EXPECT_FLOAT_EQ(e2.g, 0.0f); EXPECT_FLOAT_EQ(e2.b, 0.0f); e2 -= e1; EXPECT_FLOAT_EQ(e2.r, -e1.r); EXPECT_FLOAT_EQ(e2.g, -e1.g); EXPECT_FLOAT_EQ(e2.b, -e1.b); } TEST_F(GainMapMathTest, ColorSubtractFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 - 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f); EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f); EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f); e2 -= 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f); EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f); EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f); } TEST_F(GainMapMathTest, ColorMultiplyFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 * 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); e2 *= 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f); EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f); EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f); } TEST_F(GainMapMathTest, ColorDivideFloat) { Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; Color e2 = e1 / 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f); EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f); EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f); e2 /= 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f); EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f); EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f); } 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); } TEST_F(GainMapMathTest, SrgbYuvToRgb) { Color rgb_black = srgbYuvToRgb(YuvBlack()); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = srgbYuvToRgb(YuvWhite()); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = srgbYuvToRgb(SrgbYuvRed()); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = srgbYuvToRgb(SrgbYuvGreen()); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = srgbYuvToRgb(SrgbYuvBlue()); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } TEST_F(GainMapMathTest, SrgbRgbToYuv) { Color yuv_black = srgbRgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv_black, YuvBlack()); Color yuv_white = srgbRgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv_white, YuvWhite()); Color yuv_r = srgbRgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed()); Color yuv_g = srgbRgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen()); Color yuv_b = srgbRgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue()); } TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) { Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack())); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite())); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed())); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen())); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue())); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } TEST_F(GainMapMathTest, SrgbTransferFunction) { EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f); EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon()); EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon()); EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f); } 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); } TEST_F(GainMapMathTest, P3YuvToRgb) { Color rgb_black = p3YuvToRgb(YuvBlack()); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = p3YuvToRgb(YuvWhite()); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = p3YuvToRgb(P3YuvRed()); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = p3YuvToRgb(P3YuvGreen()); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = p3YuvToRgb(P3YuvBlue()); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } TEST_F(GainMapMathTest, P3RgbToYuv) { Color yuv_black = p3RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv_black, YuvBlack()); Color yuv_white = p3RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv_white, YuvWhite()); Color yuv_r = p3RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv_r, P3YuvRed()); Color yuv_g = p3RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv_g, P3YuvGreen()); Color yuv_b = p3RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv_b, P3YuvBlue()); } TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) { Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack())); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite())); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed())); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen())); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue())); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } 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); } TEST_F(GainMapMathTest, Bt2100YuvToRgb) { Color rgb_black = bt2100YuvToRgb(YuvBlack()); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = bt2100YuvToRgb(YuvWhite()); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed()); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen()); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue()); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } TEST_F(GainMapMathTest, Bt2100RgbToYuv) { Color yuv_black = bt2100RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv_black, YuvBlack()); Color yuv_white = bt2100RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv_white, YuvWhite()); Color yuv_r = bt2100RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed()); Color yuv_g = bt2100RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen()); Color yuv_b = bt2100RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue()); } TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) { Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack())); EXPECT_RGB_NEAR(rgb_black, RgbBlack()); Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite())); EXPECT_RGB_NEAR(rgb_white, RgbWhite()); Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed())); EXPECT_RGB_NEAR(rgb_r, RgbRed()); Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen())); EXPECT_RGB_NEAR(rgb_g, RgbGreen()); Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue())); EXPECT_RGB_NEAR(rgb_b, RgbBlue()); } TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) { Color yuv_black = srgbRgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack()); Color yuv_white = srgbRgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite()); Color yuv_r = srgbRgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed()); Color yuv_g = srgbRgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen()); Color yuv_b = srgbRgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue()); } TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) { Color yuv_black = srgbRgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack()); Color yuv_white = srgbRgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite()); Color yuv_r = srgbRgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed()); Color yuv_g = srgbRgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen()); Color yuv_b = srgbRgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue()); } TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) { Color yuv_black = p3RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack()); Color yuv_white = p3RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite()); Color yuv_r = p3RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed()); Color yuv_g = p3RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen()); Color yuv_b = p3RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue()); } TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) { Color yuv_black = p3RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack()); Color yuv_white = p3RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite()); Color yuv_r = p3RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed()); Color yuv_g = p3RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen()); Color yuv_b = p3RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue()); } TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) { Color yuv_black = bt2100RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack()); Color yuv_white = bt2100RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite()); Color yuv_r = bt2100RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed()); Color yuv_g = bt2100RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen()); Color yuv_b = bt2100RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue()); } TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) { Color yuv_black = bt2100RgbToYuv(RgbBlack()); EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack()); Color yuv_white = bt2100RgbToYuv(RgbWhite()); EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite()); Color yuv_r = bt2100RgbToYuv(RgbRed()); EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed()); Color yuv_g = bt2100RgbToYuv(RgbGreen()); EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen()); Color yuv_b = bt2100RgbToYuv(RgbBlue()); EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue()); } TEST_F(GainMapMathTest, TransformYuv420) { ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100, yuv2100To709, yuv2100To601 }; for (const ColorTransformFn& transform : transforms) { jpegr_uncompressed_struct input = Yuv420Image(); size_t out_buf_size = input.width * input.height * 3 / 2; std::unique_ptr out_buf = std::make_unique(out_buf_size); memcpy(out_buf.get(), input.data, out_buf_size); jpegr_uncompressed_struct output = Yuv420Image(); output.data = out_buf.get(); transformYuv420(&output, 1, 1, transform); for (size_t y = 0; y < 4; ++y) { for (size_t x = 0; x < 4; ++x) { // Skip the last chroma sample, which we modified above if (x >= 2 && y >= 2) { continue; } // All other pixels should remain unchanged EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y)); } } // modified pixels should be updated as intended by the transformYuv420 algorithm Color in1 = getYuv420Pixel(&input, 2, 2); Color in2 = getYuv420Pixel(&input, 3, 2); Color in3 = getYuv420Pixel(&input, 2, 3); Color in4 = getYuv420Pixel(&input, 3, 3); Color out1 = getYuv420Pixel(&output, 2, 2); Color out2 = getYuv420Pixel(&output, 3, 2); Color out3 = getYuv420Pixel(&output, 2, 3); Color out4 = getYuv420Pixel(&output, 3, 3); EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon()); EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon()); EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon()); EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon()); Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f; EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon()); EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon()); } } TEST_F(GainMapMathTest, HlgOetf) { EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f); EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon()); EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon()); EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f); Color e = {{{ 0.04167f, 0.08333f, 0.5f }}}; Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}}; EXPECT_RGB_NEAR(hlgOetf(e), e_gamma); } TEST_F(GainMapMathTest, HlgInvOetf) { EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f); EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f); Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}}; Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}}; EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e); } TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f); EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon()); EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f); } TEST_F(GainMapMathTest, PqOetf) { EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f); EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon()); EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon()); EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f); Color e = {{{ 0.01f, 0.5f, 0.99f }}}; Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}}; EXPECT_RGB_NEAR(pqOetf(e), e_gamma); } TEST_F(GainMapMathTest, PqInvOetf) { EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f); EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f); Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}}; Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}}; EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e); } TEST_F(GainMapMathTest, PqInvOetfLUT) { for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); } } TEST_F(GainMapMathTest, HlgInvOetfLUT) { for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); } } TEST_F(GainMapMathTest, pqOetfLUT) { for (int idx = 0; idx < kPqOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); } } TEST_F(GainMapMathTest, hlgOetfLUT) { for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); } } TEST_F(GainMapMathTest, srgbInvOetfLUT) { for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); } } TEST_F(GainMapMathTest, applyGainLUT) { for (int boost = 1; boost <= 10; boost++) { ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / static_cast(boost) }; GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), applyGainLUT(RgbBlack(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), applyGainLUT(RgbWhite(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), applyGainLUT(RgbRed(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), applyGainLUT(RgbGreen(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), applyGainLUT(RgbBlue(), value, gainLUT)); EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), applyGainLUT(RgbRed(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } for (int boost = 1; boost <= 10; boost++) { ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f }; GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), applyGainLUT(RgbBlack(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), applyGainLUT(RgbWhite(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), applyGainLUT(RgbRed(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), applyGainLUT(RgbGreen(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), applyGainLUT(RgbBlue(), value, gainLUT)); EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), applyGainLUT(RgbRed(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } for (int boost = 1; boost <= 10; boost++) { ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), .minContentBoost = 1.0f / pow(static_cast(boost), 1.0f / 3.0f) }; GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (int idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), applyGainLUT(RgbBlack(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), applyGainLUT(RgbWhite(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), applyGainLUT(RgbRed(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), applyGainLUT(RgbGreen(), value, gainLUT)); EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), applyGainLUT(RgbBlue(), value, gainLUT)); EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), applyGainLUT(RgbRed(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); } } } TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) { EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f); EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon()); EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f); } TEST_F(GainMapMathTest, ColorConversionLookup) { EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709), identityConversion); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), p3ToBt709); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToBt709); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), bt709ToP3); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), identityConversion); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToP3); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709), bt709ToBt2100); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), p3ToBt2100); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100), identityConversion); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100), nullptr); } TEST_F(GainMapMathTest, EncodeGain) { ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127); EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127); EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255); EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255); EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191); EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63); metadata.maxContentBoost = 2.0f; metadata.minContentBoost = 1.0f / 2.0f; EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255); EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191); EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f / 8.0f; EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191); EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170); EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 0.5f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63); EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63); EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191); EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127); EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31); EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0); } TEST_F(GainMapMathTest, ApplyGain) { ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, .minContentBoost = 1.0f / 4.0f }; float displayBoost = metadata.maxContentBoost; EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); metadata.maxContentBoost = 2.0f; metadata.minContentBoost = 1.0f / 2.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f / 8.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 0.5f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); Color e = {{{ 0.0f, 0.5f, 1.0f }}}; metadata.maxContentBoost = 4.0f; metadata.minContentBoost = 1.0f / 4.0f; EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f); EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f); EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e); EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f); EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f); EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata), applyGain(RgbBlack(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata), applyGain(RgbWhite(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata), applyGain(RgbRed(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata), applyGain(RgbGreen(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata), applyGain(RgbBlue(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata), applyGain(e, 1.0f, &metadata, displayBoost)); } TEST_F(GainMapMathTest, GetYuv420Pixel) { jpegr_uncompressed_struct image = Yuv420Image(); Color (*colors)[4] = Yuv420Colors(); for (size_t y = 0; y < 4; ++y) { for (size_t x = 0; x < 4; ++x) { EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]); } } } TEST_F(GainMapMathTest, GetP010Pixel) { jpegr_uncompressed_struct image = P010Image(); Color (*colors)[4] = P010Colors(); for (size_t y = 0; y < 4; ++y) { for (size_t x = 0; x < 4; ++x) { EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]); } } } TEST_F(GainMapMathTest, SampleYuv420) { jpegr_uncompressed_struct image = Yuv420Image(); Color (*colors)[4] = Yuv420Colors(); static const size_t kMapScaleFactor = 2; for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { Color min = {{{ 1.0f, 1.0f, 1.0f }}}; Color max = {{{ -1.0f, -1.0f, -1.0f }}}; for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; min = ColorMin(min, e); max = ColorMax(max, e); } } // Instead of reimplementing the sampling algorithm, confirm that the // sample output is within the range of the min and max of the nearest // points. EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max); } } } TEST_F(GainMapMathTest, SampleP010) { jpegr_uncompressed_struct image = P010Image(); Color (*colors)[4] = P010Colors(); static const size_t kMapScaleFactor = 2; for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { Color min = {{{ 1.0f, 1.0f, 1.0f }}}; Color max = {{{ -1.0f, -1.0f, -1.0f }}}; for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; min = ColorMin(min, e); max = ColorMax(max, e); } } // Instead of reimplementing the sampling algorithm, confirm that the // sample output is within the range of the min and max of the nearest // points. EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max); } } } TEST_F(GainMapMathTest, SampleMap) { jpegr_uncompressed_struct image = MapImage(); float (*values)[4] = MapValues(); static const size_t kMapScaleFactor = 2; ShepardsIDW idwTable(kMapScaleFactor); for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) { for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) { size_t x_base = x / kMapScaleFactor; size_t y_base = y / kMapScaleFactor; float min = 1.0f; float max = -1.0f; min = fmin(min, values[y_base][x_base]); max = fmax(max, values[y_base][x_base]); if (y_base + 1 < 4) { min = fmin(min, values[y_base + 1][x_base]); max = fmax(max, values[y_base + 1][x_base]); } if (x_base + 1 < 4) { min = fmin(min, values[y_base][x_base + 1]); max = fmax(max, values[y_base][x_base + 1]); } if (y_base + 1 < 4 && x_base + 1 < 4) { min = fmin(min, values[y_base + 1][x_base + 1]); max = fmax(max, values[y_base + 1][x_base + 1]); } // Instead of reimplementing the sampling algorithm, confirm that the // sample output is within the range of the min and max of the nearest // points. EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y), testing::AllOf(testing::Ge(min), testing::Le(max))); EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable), sampleMap(&image, kMapScaleFactor, x, y)); } } } TEST_F(GainMapMathTest, ColorToRgba1010102) { EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30); EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF); EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff); EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10); EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20); Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; EXPECT_EQ(colorToRgba1010102(e_gamma), 0x3 << 30 | static_cast(0.1f * static_cast(0x3ff)) | static_cast(0.2f * static_cast(0x3ff)) << 10 | static_cast(0.3f * static_cast(0x3ff)) << 20); } TEST_F(GainMapMathTest, ColorToRgbaF16) { EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48); EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00); EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00)); EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16)); EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32)); Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66); } TEST_F(GainMapMathTest, Float32ToFloat16) { EXPECT_EQ(floatToHalf(0.1f), 0x2E66); EXPECT_EQ(floatToHalf(0.0f), 0x0); EXPECT_EQ(floatToHalf(1.0f), 0x3C00); EXPECT_EQ(floatToHalf(-1.0f), 0xBC00); EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF); // float min EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), kSdrWhiteNits); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance), srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance), srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance), srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), kSdrWhiteNits); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance), p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance), p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) { EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), 0.0f); EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), kSdrWhiteNits); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance), bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance), bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance), bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) { EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), 0.0f); EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), kHlgMaxNits); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon()); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon()); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, GenerateMapLuminancePq) { EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), 0.0f); EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), kPqMaxNits); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon()); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon()); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, ApplyMap) { ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f, .minContentBoost = 1.0f / 8.0f }; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), RgbBlack()); EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), RgbRed() * 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), RgbGreen() * 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), RgbBlue() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), RgbWhite() * sqrt(8.0f)); EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), RgbBlack()); EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), RgbRed() * sqrt(8.0f)); EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), RgbGreen() * sqrt(8.0f)); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), RgbBlue() * sqrt(8.0f)); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite()); EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), RgbBlack()); EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), RgbRed()); EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), RgbGreen()); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), RgbBlue()); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite() / sqrt(8.0f)); EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), RgbBlack()); EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), RgbRed() / sqrt(8.0f)); EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), RgbGreen() / sqrt(8.0f)); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), RgbBlue() / sqrt(8.0f)); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), RgbRed() / 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite()); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 0.5f;; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite()); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); } } // namespace android::ultrahdr