| // 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 "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 char* file_name = 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()); |
| |
| const testutil::AvifImagePtr image = testutil::ReadImage( |
| data_path, file_name, AVIF_PIXEL_FORMAT_NONE, 0, AVIF_RGB_TO_YUV_DEFAULT, |
| !use_icc, !use_exif, !use_xmp); |
| ASSERT_NE(image, nullptr); |
| 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) { |
| const testutil::AvifImagePtr ref = |
| testutil::ReadImage(data_path, "paris_icc_exif_xmp.png"); |
| ASSERT_NE(ref, nullptr); |
| EXPECT_GT(ref->exif.size, 0u); |
| EXPECT_GT(ref->xmp.size, 0u); |
| EXPECT_GT(ref->icc.size, 0u); |
| |
| for (const char* file_name : |
| {"paris_exif_xmp_icc.jpg", "paris_icc_exif_xmp_at_end.png"}) { |
| const testutil::AvifImagePtr image = |
| testutil::ReadImage(data_path, file_name); |
| ASSERT_NE(image, nullptr); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->exif, ref->exif)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->xmp, ref->xmp)); |
| EXPECT_TRUE(testutil::AreByteSequencesEqual(image->icc, ref->icc)); |
| } |
| } |
| |
| // A test for https://github.com/AOMediaCodec/libavif/issues/1086 to prevent |
| // regression. |
| TEST(MetadataTest, DecoderParseICC) { |
| std::string file_path = std::string(data_path) + "paris_icc_exif_xmp.avif"; |
| avifDecoder* decoder = avifDecoderCreate(); |
| EXPECT_EQ(avifDecoderSetIOFile(decoder, file_path.c_str()), AVIF_RESULT_OK); |
| EXPECT_EQ(avifDecoderParse(decoder), AVIF_RESULT_OK); |
| // Check the first four bytes of the ICC profile. |
| ASSERT_GE(decoder->image->icc.size, 4u); |
| EXPECT_EQ(decoder->image->icc.data[0], 0); |
| EXPECT_EQ(decoder->image->icc.data[1], 0); |
| EXPECT_EQ(decoder->image->icc.data[2], 2); |
| EXPECT_EQ(decoder->image->icc.data[3], 84); |
| avifDecoderDestroy(decoder); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| } // 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(); |
| } |