blob: 7c2d076992b4f919e615db9fa3b5ee6e5714e6dd [file] [log] [blame]
/*
* 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 <cmath>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <ultrahdr/gainmapmath.h>
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<float>(y) / 255.0f,
(static_cast<float>(u) - 128.0f) / 255.0f,
(static_cast<float>(v) - 128.0f) / 255.0f }}};
}
Color P010(uint16_t y, uint16_t u, uint16_t v) {
return {{{ (static_cast<float>(y) - 64.0f) / 876.0f,
(static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
(static_cast<float>(v) - 64.0f) / 896.0f - 0.5f }}};
}
float Map(uint8_t e) {
return static_cast<float>(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, pixels + 16, 4, 2 };
}
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, pixels + 16, 4, 4 };
}
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<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
memcpy(out_buf.get(), input.data, out_buf_size);
jpegr_uncompressed_struct output = Yuv420Image();
output.data = out_buf.get();
output.chroma_data = out_buf.get() + input.width * input.height;
output.luma_stride = input.width;
output.chroma_stride = input.width / 2;
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<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
}
}
TEST_F(GainMapMathTest, HlgInvOetfLUT) {
for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
}
}
TEST_F(GainMapMathTest, pqOetfLUT) {
for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
}
}
TEST_F(GainMapMathTest, hlgOetfLUT) {
for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
}
}
TEST_F(GainMapMathTest, srgbInvOetfLUT) {
for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
.minContentBoost = 1.0f / static_cast<float>(boost) };
GainLUT gainLUT(&metadata);
GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
.minContentBoost = 1.0f };
GainLUT gainLUT(&metadata);
GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
.minContentBoost = 1.0f / pow(static_cast<float>(boost),
1.0f / 3.0f) };
GainLUT gainLUT(&metadata);
GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
float value = static_cast<float>(idx) / static_cast<float>(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<uint32_t>(0.1f * static_cast<float>(0x3ff))
| static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
| static_cast<uint32_t>(0.3f * static_cast<float>(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