| // Copyright 2023 Google LLC |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include <cmath> |
| #include <fstream> |
| #include <iostream> |
| |
| #include "avif/internal.h" |
| #include "aviftest_helpers.h" |
| #include "gtest/gtest.h" |
| |
| namespace avif { |
| namespace { |
| |
| // Used to pass the data folder path to the GoogleTest suites. |
| const char* data_path = nullptr; |
| |
| void ExpectMatrixNear(const double actual[3][3], |
| const std::array<std::array<double, 3>, 3>& expected, |
| const double epsilon) { |
| EXPECT_NEAR(actual[0][0], expected[0][0], epsilon); |
| EXPECT_NEAR(actual[0][1], expected[0][1], epsilon); |
| EXPECT_NEAR(actual[0][2], expected[0][2], epsilon); |
| EXPECT_NEAR(actual[1][0], expected[1][0], epsilon); |
| EXPECT_NEAR(actual[1][1], expected[1][1], epsilon); |
| EXPECT_NEAR(actual[1][2], expected[1][2], epsilon); |
| EXPECT_NEAR(actual[2][0], expected[2][0], epsilon); |
| EXPECT_NEAR(actual[2][1], expected[2][1], epsilon); |
| EXPECT_NEAR(actual[2][2], expected[2][2], epsilon); |
| } |
| |
| TEST(RgbToXyzD50Matrix, GoldenValues) { |
| double coeffs[3][3]; |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToXYZD50Matrix( |
| AVIF_COLOR_PRIMARIES_BT709, coeffs)); |
| // Golden values from |
| // http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html |
| const double kEpsilon = 0.00015; |
| ExpectMatrixNear(coeffs, |
| {{{0.4360747, 0.3850649, 0.1430804}, |
| {0.2225045, 0.7168786, 0.0606169}, |
| {0.0139322, 0.0971045, 0.7141733}}}, |
| kEpsilon); |
| } |
| |
| TEST(XyzD50ToRgbMatrix, GoldenValues) { |
| double coeffs[3][3]; |
| ASSERT_TRUE(avifColorPrimariesComputeXYZD50ToRGBMatrix( |
| AVIF_COLOR_PRIMARIES_BT709, coeffs)); |
| // Golden values from |
| // http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html |
| // Higher tolerance than for the RGB->XYZ matrix because of compounding |
| // computation error (we compute the RGB->XYZ matrix then invert it which adds |
| // more error). |
| const double kEpsilon = 0.0005; |
| ExpectMatrixNear(coeffs, |
| {{{3.1338561, -1.6168667, -0.4906146}, |
| {-0.9787684, 1.9161415, 0.0334540}, |
| {0.0719453, -0.2289914, 1.4052427}}}, |
| kEpsilon); |
| } |
| |
| TEST(RgbToRgbConversion, Identity) { |
| const double kEpsilon = 0.000001; |
| for (int primaries = AVIF_COLOR_PRIMARIES_UNKNOWN; |
| primaries <= AVIF_COLOR_PRIMARIES_SMPTE432; ++primaries) { |
| double coeffs[3][3]; |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToRGBMatrix( |
| (avifColorPrimaries)primaries, (avifColorPrimaries)primaries, coeffs)); |
| ExpectMatrixNear(coeffs, |
| {{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}}, |
| kEpsilon); |
| } |
| } |
| |
| TEST(ColorPrimariesComputeRGBToRGBMatrix, GoldenValues) { |
| // Golden values from http://color.support/colorspacecalculator.html |
| double coeffs[3][3]; |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToRGBMatrix( |
| AVIF_COLOR_PRIMARIES_BT709, AVIF_COLOR_PRIMARIES_BT2020, coeffs)); |
| const double kEpsilon = 0.0001; |
| ExpectMatrixNear(coeffs, |
| {{{0.627404, 0.329283, 0.043313}, |
| {0.069097, 0.919540, 0.011362}, |
| {0.016391, 0.088013, 0.895595}}}, |
| kEpsilon); |
| |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToRGBMatrix( |
| AVIF_COLOR_PRIMARIES_BT2020, AVIF_COLOR_PRIMARIES_BT709, coeffs)); |
| ExpectMatrixNear(coeffs, |
| {{{1.660491, -0.587641, -0.072850}, |
| {-0.124550, 1.132900, -0.008349}, |
| {-0.018151, -0.100579, 1.118730}}}, |
| kEpsilon); |
| |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToRGBMatrix( |
| AVIF_COLOR_PRIMARIES_BT709, AVIF_COLOR_PRIMARIES_XYZ, coeffs)); |
| ExpectMatrixNear(coeffs, |
| {{{0.438449, 0.392176, 0.169375}, |
| {0.222828, 0.708691, 0.068481}, |
| {0.017314, 0.110445, 0.872241}}}, |
| kEpsilon); |
| } |
| |
| using ConvertImageColorspaceTest = ::testing::TestWithParam< |
| std::tuple</*src_image_name=*/std::string, |
| /*reference_image_name=*/std::string, /*min_psnr=*/float>>; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Parameterized, ConvertImageColorspaceTest, |
| ::testing::Values( |
| std::make_tuple(/*src_image_name=*/"colors_hdr_rec2020.avif", |
| /*reference_image_name=*/"colors_hdr_srgb.avif", |
| /*min_psnr=*/44.0f), |
| std::make_tuple(/*src_image_name=*/"colors_hdr_srgb.avif", |
| /*reference_image_name=*/"colors_hdr_rec2020.avif", |
| /*min_psnr=*/44.0f), |
| std::make_tuple(/*src_image_name=*/"colors_hdr_rec2020.avif", |
| /*reference_image_name=*/"colors_hdr_p3.avif", |
| /*min_psnr=*/60.0f), |
| std::make_tuple(/*src_image_name=*/"colors_hdr_p3.avif", |
| /*reference_image_name=*/"colors_hdr_rec2020.avif", |
| /*min_psnr=*/44.0f), |
| std::make_tuple(/*src_image_name=*/"colors_hdr_p3.avif", |
| /*reference_image_name=*/"colors_hdr_srgb.avif", |
| /*min_psnr=*/44.0f), |
| std::make_tuple(/*src_image_name=*/"colors_hdr_srgb.avif", |
| /*reference_image_name=*/"colors_hdr_p3.avif", |
| /*min_psnr=*/60.0f))); |
| |
| TEST_P(ConvertImageColorspaceTest, ConvertImage) { |
| const std::string src_image_name = std::get<0>(GetParam()); |
| const std::string reference_image_name = std::get<1>(GetParam()); |
| const float min_psnr = std::get<2>(GetParam()); |
| |
| const ImagePtr src_image = |
| testutil::DecodeFile(std::string(data_path) + src_image_name); |
| ASSERT_NE(src_image, nullptr) |
| << "Failed to read " << std::string(data_path) + src_image_name; |
| testutil::AvifRgbImage src_image_rgb(src_image.get(), src_image->depth, |
| AVIF_RGB_FORMAT_RGB); |
| ASSERT_EQ(avifImageYUVToRGB(src_image.get(), &src_image_rgb), AVIF_RESULT_OK); |
| |
| const ImagePtr reference_image = |
| testutil::DecodeFile(std::string(data_path) + reference_image_name); |
| ASSERT_NE(reference_image, nullptr); |
| testutil::AvifRgbImage reference_image_rgb( |
| reference_image.get(), reference_image->depth, AVIF_RGB_FORMAT_RGB); |
| ASSERT_EQ(avifImageYUVToRGB(reference_image.get(), &reference_image_rgb), |
| AVIF_RESULT_OK); |
| |
| ASSERT_EQ(reference_image_rgb.width, src_image_rgb.width); |
| ASSERT_EQ(reference_image_rgb.height, src_image_rgb.height); |
| |
| avifRGBColorSpaceInfo src_info; |
| ASSERT_TRUE(avifGetRGBColorSpaceInfo(&src_image_rgb, &src_info)); |
| |
| testutil::AvifRgbImage src_image_converted_rgb( |
| reference_image.get(), reference_image_rgb.depth, AVIF_RGB_FORMAT_RGB); |
| avifRGBColorSpaceInfo dst_info; |
| ASSERT_TRUE(avifGetRGBColorSpaceInfo(&src_image_converted_rgb, &dst_info)); |
| |
| const avifTransferFunction gammaToLinear = |
| avifTransferCharacteristicsGetGammaToLinearFunction( |
| src_image->transferCharacteristics); |
| const avifTransferFunction linearToGamma = |
| avifTransferCharacteristicsGetLinearToGammaFunction( |
| reference_image->transferCharacteristics); |
| |
| double coeffs[3][3]; |
| ASSERT_TRUE(avifColorPrimariesComputeRGBToRGBMatrix( |
| src_image->colorPrimaries, reference_image->colorPrimaries, coeffs)); |
| |
| for (uint32_t j = 0; j < src_image_rgb.height; ++j) { |
| for (uint32_t i = 0; i < src_image_rgb.width; ++i) { |
| float rgba[4]; |
| avifGetRGBAPixel(&src_image_rgb, i, j, &src_info, rgba); |
| for (int c = 0; c < 3; ++c) rgba[c] = gammaToLinear(rgba[c]); |
| avifLinearRGBConvertColorSpace(rgba, coeffs); |
| for (int c = 0; c < 3; ++c) rgba[c] = linearToGamma(rgba[c]); |
| avifSetRGBAPixel(&src_image_converted_rgb, i, j, &dst_info, rgba); |
| } |
| } |
| |
| ImagePtr src_image_converted( |
| avifImageCreate(reference_image->width, reference_image->height, |
| reference_image->depth, reference_image->yuvFormat)); |
| ASSERT_EQ( |
| avifImageRGBToYUV(src_image_converted.get(), &src_image_converted_rgb), |
| AVIF_RESULT_OK); |
| src_image_converted->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709; |
| src_image_converted->transferCharacteristics = |
| src_image->transferCharacteristics; |
| src_image_converted->clli = src_image->clli; |
| |
| const double psnr = testutil::GetPsnr(*reference_image, *src_image_converted); |
| std::cout << "PSNR: " << psnr << "\n"; |
| EXPECT_GT(psnr, min_psnr); |
| |
| // Uncomment the following to save the encoded image as an AVIF file. |
| // const auto encoded = |
| // testutil::Encode(src_image_converted.get(), 9, 100); |
| // std::ofstream("/tmp/colrtest.avif", std::ios::binary) |
| // .write(reinterpret_cast<char*>(encoded.data), encoded.size); |
| } |
| |
| } // namespace |
| } // namespace avif |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| if (argc != 2) { |
| std::cerr << "There must be exactly one argument containing the path to " |
| "the test data folder" |
| << std::endl; |
| return 1; |
| } |
| avif::data_path = argv[1]; |
| return RUN_ALL_TESTS(); |
| } |