| // Copyright 2022 Google LLC. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| #include <array> |
| #include <tuple> |
| |
| #include "avif/avif.h" |
| #include "aviftest_helpers.h" |
| #include "avifutil.h" |
| #include "gtest/gtest.h" |
| |
| using ::testing::Bool; |
| using ::testing::Combine; |
| using ::testing::Values; |
| |
| namespace libavif { |
| namespace { |
| |
| //------------------------------------------------------------------------------ |
| |
| // Used to pass the data folder path to the GoogleTest suites. |
| const char* data_path = nullptr; |
| |
| // ICC color profiles are not checked by libavif so the content does not matter. |
| // This is a truncated widespread ICC color profile. |
| const std::array<uint8_t, 24> kSampleIcc = { |
| 0x00, 0x00, 0x02, 0x0c, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x10, 0x00, 0x00, |
| 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20}; |
| |
| // Exif bytes are partially checked by libavif. This is a truncated widespread |
| // Exif metadata chunk. |
| const std::array<uint8_t, 24> kSampleExif = { |
| 0xff, 0x1, 0x45, 0x78, 0x69, 0x76, 0x32, 0xff, 0xe1, 0x12, 0x5a, 0x45, |
| 0x78, 0x69, 0x66, 0x0, 0x0, 0x49, 0x49, 0x2a, 0x0, 0x8, 0x0, 0x0}; |
| |
| // XMP bytes are not checked by libavif so the content does not matter. |
| // This is a truncated widespread XMP metadata chunk. |
| const std::array<uint8_t, 24> kSampleXmp = { |
| 0x3c, 0x3f, 0x78, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x62, 0x65, |
| 0x67, 0x69, 0x6e, 0x3d, 0x22, 0xef, 0xbb, 0xbf, 0x22, 0x20, 0x69, 0x64}; |
| |
| //------------------------------------------------------------------------------ |
| // AVIF encode/decode metadata tests |
| |
| class AvifMetadataTest |
| : public testing::TestWithParam< |
| std::tuple</*use_icc=*/bool, /*use_exif=*/bool, /*use_xmp=*/bool>> {}; |
| |
| // Encodes, decodes then verifies that the output metadata matches the input |
| // metadata defined by the parameters. |
| TEST_P(AvifMetadataTest, EncodeDecode) { |
| const bool use_icc = std::get<0>(GetParam()); |
| const bool use_exif = std::get<1>(GetParam()); |
| const bool use_xmp = std::get<2>(GetParam()); |
| |
| testutil::AvifImagePtr image = |
| testutil::CreateImage(/*width=*/12, /*height=*/34, /*depth=*/10, |
| AVIF_PIXEL_FORMAT_YUV444, AVIF_PLANES_ALL); |
| ASSERT_NE(image, nullptr); |
| testutil::FillImageGradient(image.get()); // The pixels do not matter. |
| if (use_icc) { |
| avifImageSetProfileICC(image.get(), kSampleIcc.data(), kSampleIcc.size()); |
| } |
| if (use_exif) { |
| avifImageSetMetadataExif(image.get(), kSampleExif.data(), |
| kSampleExif.size()); |
| } |
| if (use_xmp) { |
| avifImageSetMetadataXMP(image.get(), kSampleXmp.data(), kSampleXmp.size()); |
| } |
| |
| // Encode. |
| testutil::AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy); |
| ASSERT_NE(encoder, nullptr); |
| encoder->speed = AVIF_SPEED_FASTEST; |
| testutil::AvifRwData encoded_avif; |
| ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded_avif), |
| AVIF_RESULT_OK); |
| |
| // Decode. |
| testutil::AvifImagePtr decoded(avifImageCreateEmpty(), avifImageDestroy); |
| ASSERT_NE(decoded, nullptr); |
| testutil::AvifDecoderPtr decoder(avifDecoderCreate(), avifDecoderDestroy); |
| ASSERT_NE(decoder, nullptr); |
| ASSERT_EQ(avifDecoderReadMemory(decoder.get(), decoded.get(), |
| encoded_avif.data, encoded_avif.size), |
| AVIF_RESULT_OK); |
| |
| // Compare input and output metadata. |
| EXPECT_TRUE(testutil::AreByteSequencesEqual( |
| decoded->icc.data, decoded->icc.size, kSampleIcc.data(), |
| use_icc ? kSampleIcc.size() : 0u)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual( |
| decoded->exif.data, decoded->exif.size, kSampleExif.data(), |
| use_exif ? kSampleExif.size() : 0u)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual( |
| decoded->xmp.data, decoded->xmp.size, kSampleXmp.data(), |
| use_xmp ? kSampleXmp.size() : 0u)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, AvifMetadataTest, |
| Combine(/*use_icc=*/Bool(), /*use_exif=*/Bool(), |
| /*use_xmp=*/Bool())); |
| |
| //------------------------------------------------------------------------------ |
| // Jpeg and PNG metadata tests |
| |
| class MetadataTest |
| : public testing::TestWithParam< |
| std::tuple</*file_name=*/const char*, /*use_icc=*/bool, |
| /*use_exif=*/bool, /*use_xmp=*/bool, /*expect_icc=*/bool, |
| /*expect_exif=*/bool, /*expect_xmp=*/bool>> {}; |
| |
| // zTXt "Raw profile type exif" at the beginning of a PNG file. |
| TEST_P(MetadataTest, Read) { |
| const std::string file_path = |
| std::string(data_path) + "/" + std::get<0>(GetParam()); |
| const bool use_icc = std::get<1>(GetParam()); |
| const bool use_exif = std::get<2>(GetParam()); |
| const bool use_xmp = std::get<3>(GetParam()); |
| const bool expect_icc = std::get<4>(GetParam()); |
| const bool expect_exif = std::get<5>(GetParam()); |
| const bool expect_xmp = std::get<6>(GetParam()); |
| |
| avifImage* image = avifImageCreateEmpty(); |
| ASSERT_NE(image, nullptr); |
| image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // lossless |
| ASSERT_NE(avifReadImage(file_path.c_str(), AVIF_PIXEL_FORMAT_NONE, 0, |
| AVIF_RGB_TO_YUV_DEFAULT, !use_icc, !use_exif, |
| !use_xmp, image, nullptr, nullptr, nullptr), |
| AVIF_APP_FILE_FORMAT_UNKNOWN); |
| EXPECT_NE(image->width * image->height, 0u); |
| if (expect_icc) { |
| EXPECT_NE(image->icc.size, 0u); |
| EXPECT_NE(image->icc.data, nullptr); |
| } else { |
| EXPECT_EQ(image->icc.size, 0u); |
| EXPECT_EQ(image->icc.data, nullptr); |
| } |
| if (expect_exif) { |
| EXPECT_NE(image->exif.size, 0u); |
| EXPECT_NE(image->exif.data, nullptr); |
| } else { |
| EXPECT_EQ(image->exif.size, 0u); |
| EXPECT_EQ(image->exif.data, nullptr); |
| } |
| if (expect_xmp) { |
| EXPECT_NE(image->xmp.size, 0u); |
| EXPECT_NE(image->xmp.data, nullptr); |
| } else { |
| EXPECT_EQ(image->xmp.size, 0u); |
| EXPECT_EQ(image->xmp.data, nullptr); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PngNone, MetadataTest, |
| Combine(Values("paris_icc_exif_xmp.png"), // iCCP zTXt zTXt IDAT |
| /*use_icc=*/Values(false), /*use_exif=*/Values(false), |
| /*use_xmp=*/Values(false), /*expected_icc=*/Values(false), |
| /*expected_exif=*/Values(false), /*expected_xmp=*/Values(false))); |
| INSTANTIATE_TEST_SUITE_P( |
| PngAll, MetadataTest, |
| Combine(Values("paris_icc_exif_xmp.png"), // iCCP zTXt zTXt IDAT |
| /*use_icc=*/Values(true), /*use_exif=*/Values(true), |
| /*use_xmp=*/Values(true), /*expected_icc=*/Values(true), |
| /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PngExifAtEnd, MetadataTest, |
| Combine(Values("paris_icc_exif_xmp_at_end.png"), // iCCP IDAT eXIf tEXt |
| /*use_icc=*/Values(true), /*use_exif=*/Values(true), |
| /*use_xmp=*/Values(true), /*expected_icc=*/Values(true), |
| /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Jpeg, MetadataTest, |
| Combine(Values("paris_exif_xmp_icc.jpg"), // APP1-Exif, APP1-XMP, APP2-ICC |
| /*use_icc=*/Values(true), /*use_exif=*/Values(true), |
| /*use_xmp=*/Values(true), /*expected_icc=*/Values(true), |
| /*expected_exif=*/Values(true), /*expected_xmp=*/Values(true))); |
| |
| // Verify all parsers lead exactly to the same metadata bytes. |
| TEST(MetadataTest, Compare) { |
| constexpr const char* kFileNames[] = {"paris_icc_exif_xmp.png", |
| "paris_exif_xmp_icc.jpg", |
| "paris_icc_exif_xmp_at_end.png"}; |
| avifImage* images[sizeof(kFileNames) / sizeof(kFileNames[0])]; |
| avifImage** image_it = images; |
| for (const char* file_name : kFileNames) { |
| const std::string file_path = std::string(data_path) + "/" + file_name; |
| |
| *image_it = avifImageCreateEmpty(); |
| ASSERT_NE(*image_it, nullptr); |
| ASSERT_NE(avifReadImage(file_path.c_str(), AVIF_PIXEL_FORMAT_NONE, 0, |
| AVIF_RGB_TO_YUV_DEFAULT, /*ignoreICC=*/false, |
| /*ignoreExif=*/false, /*ignoreXMP=*/false, |
| *image_it, nullptr, nullptr, nullptr), |
| AVIF_APP_FILE_FORMAT_UNKNOWN); |
| ++image_it; |
| } |
| |
| for (avifImage* image : images) { |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, images[0]->exif)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, images[0]->xmp)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->icc, images[0]->icc)); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| } // namespace |
| } // namespace libavif |
| |
| 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; |
| } |
| libavif::data_path = argv[1]; |
| return RUN_ALL_TESTS(); |
| } |