| // Copyright 2024 Google LLC |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include <cstring> |
| |
| #include "avif/avif.h" |
| #include "avif/avif_cxx.h" |
| #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; |
| |
| class SampleTransformTest |
| : public testing::TestWithParam< |
| std::tuple<avifSampleTransformRecipe, avifPixelFormat, |
| /*create_alpha=*/bool, /*quality=*/int>> {}; |
| |
| //------------------------------------------------------------------------------ |
| |
| TEST_P(SampleTransformTest, Avif16bit) { |
| const avifSampleTransformRecipe recipe = std::get<0>(GetParam()); |
| const avifPixelFormat yuv_format = std::get<1>(GetParam()); |
| const bool create_alpha = std::get<2>(GetParam()); |
| const int quality = std::get<3>(GetParam()); |
| |
| const ImagePtr image = testutil::ReadImage( |
| data_path, "weld_16bit.png", yuv_format, /*requested_depth=*/16); |
| ASSERT_NE(image, nullptr); |
| if (create_alpha && !image->alphaPlane) { |
| // Simulate alpha plane with a view on luma. |
| image->alphaPlane = image->yuvPlanes[AVIF_CHAN_Y]; |
| image->alphaRowBytes = image->yuvRowBytes[AVIF_CHAN_Y]; |
| image->imageOwnsAlphaPlane = false; |
| } |
| |
| EncoderPtr encoder(avifEncoderCreate()); |
| ASSERT_NE(encoder, nullptr); |
| encoder->speed = AVIF_SPEED_FASTEST; |
| encoder->quality = quality; |
| encoder->qualityAlpha = quality; |
| encoder->sampleTransformRecipe = recipe; |
| testutil::AvifRwData encoded; |
| ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded), |
| AVIF_RESULT_OK); |
| const ImagePtr decoded = testutil::Decode(encoded.data, encoded.size); |
| ASSERT_NE(decoded, nullptr); |
| |
| ASSERT_EQ(image->depth, decoded->depth); |
| ASSERT_EQ(image->width, decoded->width); |
| ASSERT_EQ(image->height, decoded->height); |
| |
| EXPECT_GE(testutil::GetPsnr(*image, *decoded), |
| (quality == AVIF_QUALITY_LOSSLESS) ? 99.0 : 15.0); |
| |
| // Replace all 'sato' box types by "zzzz" garbage. This simulates an old |
| // decoder that does not recognize the Sample Transform feature. |
| for (size_t i = 0; i + 4 <= encoded.size; ++i) { |
| if (!std::memcmp(&encoded.data[i], "sato", 4)) { |
| std::memcpy(&encoded.data[i], "zzzz", 4); |
| } |
| } |
| const ImagePtr decoded_no_sato = testutil::Decode(encoded.data, encoded.size); |
| ASSERT_NE(decoded_no_sato, nullptr); |
| // Only the most significant bits of each sample can be retrieved. |
| // They should be encoded losslessly no matter the quantizer settings. |
| ImagePtr image_no_sato = testutil::CreateImage( |
| static_cast<int>(image->width), static_cast<int>(image->height), |
| static_cast<int>(decoded_no_sato->depth), image->yuvFormat, |
| image->alphaPlane ? AVIF_PLANES_ALL : AVIF_PLANES_YUV, image->yuvRange); |
| ASSERT_NE(image_no_sato, nullptr); |
| |
| const uint32_t shift = |
| recipe == AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B ? 8 : 4; |
| const avifImage* inputImage = image.get(); |
| // Postfix notation. |
| const avifSampleTransformToken tokens[] = { |
| {AVIF_SAMPLE_TRANSFORM_INPUT_IMAGE_ITEM_INDEX, 0, |
| /*inputImageItemIndex=*/1}, |
| {AVIF_SAMPLE_TRANSFORM_CONSTANT, 1 << shift, 0}, |
| {AVIF_SAMPLE_TRANSFORM_DIVIDE, 0, 0}}; |
| ASSERT_EQ(avifImageApplyOperations(image_no_sato.get(), |
| AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_32, 3, |
| tokens, 1, &inputImage, AVIF_PLANES_ALL), |
| AVIF_RESULT_OK); |
| EXPECT_TRUE(testutil::AreImagesEqual(*image_no_sato, *decoded_no_sato)); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Formats, SampleTransformTest, |
| testing::Combine( |
| testing::Values(AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B), |
| testing::Values(AVIF_PIXEL_FORMAT_YUV444, AVIF_PIXEL_FORMAT_YUV420, |
| AVIF_PIXEL_FORMAT_YUV400), |
| /*create_alpha=*/testing::Values(false), |
| /*quality=*/ |
| testing::Values(AVIF_QUALITY_DEFAULT))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| BitDepthExtensions, SampleTransformTest, |
| testing::Combine( |
| testing::Values(AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B, |
| AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B), |
| testing::Values(AVIF_PIXEL_FORMAT_YUV444), |
| /*create_alpha=*/testing::Values(false), |
| /*quality=*/ |
| testing::Values(AVIF_QUALITY_LOSSLESS))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Alpha, SampleTransformTest, |
| testing::Combine( |
| testing::Values(AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_8B_8B), |
| testing::Values(AVIF_PIXEL_FORMAT_YUV444), |
| /*create_alpha=*/testing::Values(true), |
| /*quality=*/ |
| testing::Values(AVIF_QUALITY_LOSSLESS))); |
| |
| // TODO(yguyon): Test grids with bit depth extensions. |
| |
| } // 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(); |
| } |