| // Copyright 2023 Google LLC | |
| // SPDX-License-Identifier: BSD-2-Clause | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include "avif/avif.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; | |
| TEST(AvifDecodeTest, ColorGridAlphaNoGrid) { | |
| if (!testutil::Av1DecoderAvailable()) { | |
| GTEST_SKIP() << "AV1 Codec unavailable, skip test."; | |
| } | |
| // Test case from https://github.com/AOMediaCodec/libavif/issues/1203. | |
| const char* file_name = "color_grid_alpha_nogrid.avif"; | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| ASSERT_EQ(avifDecoderSetIOFile(decoder.get(), | |
| (std::string(data_path) + file_name).c_str()), | |
| AVIF_RESULT_OK); | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_EQ(decoder->alphaPresent, AVIF_TRUE); | |
| EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE); | |
| EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_NE(decoder->image->alphaPlane, nullptr); | |
| EXPECT_GT(decoder->image->alphaRowBytes, 0u); | |
| } | |
| TEST(AvifDecodeTest, ImageContentToDecodeNone) { | |
| if (!testutil::Av1DecoderAvailable()) { | |
| GTEST_SKIP() << "AV1 Codec unavailable, skip test."; | |
| } | |
| for (const std::string file_name : | |
| {"paris_icc_exif_xmp.avif", "draw_points_idat.avif", | |
| "sofa_grid1x5_420.avif", "color_grid_alpha_nogrid.avif", | |
| "seine_sdr_gainmap_srgb.avif", "draw_points_idat_progressive.avif", | |
| "weld_sato_12B_8B_q0.avif"}) { | |
| for ( | |
| avifImageContentTypeFlag image_content_to_decode : { | |
| AVIF_IMAGE_CONTENT_NONE, | |
| AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS // Equivalent to NONE without | |
| // AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA. | |
| }) { | |
| SCOPED_TRACE(file_name); | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| // Do not decode anything. | |
| decoder->imageContentToDecode = image_content_to_decode; | |
| ASSERT_EQ( | |
| avifDecoderSetIOFile(decoder.get(), | |
| (std::string(data_path) + file_name).c_str()), | |
| AVIF_RESULT_OK); | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK) | |
| << decoder->diag.error; | |
| EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE); | |
| EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_NO_CONTENT); | |
| } | |
| } | |
| } | |
| TEST(AvifDecodeTest, ParseEmptyData) { | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), nullptr, 0), AVIF_RESULT_OK); | |
| // No ftyp box was seen. | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_INVALID_FTYP); | |
| } | |
| TEST(AvifDecodeTest, ImageContentToDecodeAlphaOnly) { | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), nullptr, 0), AVIF_RESULT_OK); | |
| decoder->imageContentToDecode = AVIF_IMAGE_CONTENT_ALPHA; | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_NOT_IMPLEMENTED); | |
| } | |
| TEST(AvifDecodeTest, Idat) { | |
| if (!testutil::Av1DecoderAvailable()) { | |
| GTEST_SKIP() << "AV1 Codec unavailable, skip test."; | |
| } | |
| const ImagePtr original = testutil::ReadImage(data_path, "draw_points.png"); | |
| for (const std::string file_name : | |
| {"draw_points_idat.avif", "draw_points_idat_metasize0.avif", | |
| "draw_points_idat_progressive.avif", | |
| "draw_points_idat_progressive_metasize0.avif"}) { | |
| SCOPED_TRACE(file_name); | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| ASSERT_EQ(avifDecoderSetIOFile( | |
| decoder.get(), (std::string(data_path) + file_name).c_str()), | |
| AVIF_RESULT_OK); | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_EQ(decoder->alphaPresent, AVIF_TRUE); | |
| EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_FALSE); | |
| ASSERT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_NE(decoder->image->alphaPlane, nullptr); | |
| EXPECT_GT(decoder->image->alphaRowBytes, 0u); | |
| EXPECT_EQ(testutil::GetPsnr(*original, *decoder->image), 99.0); | |
| } | |
| } | |
| // From https://crbug.com/334281983. | |
| TEST(AvifDecodeTest, PeekCompatibleFileTypeBad1) { | |
| constexpr uint8_t kData[] = {0x00, 0x00, 0x00, 0x1c, 0x66, | |
| 0x74, 0x79, 0x70, 0x84, 0xca}; | |
| avifROData input = {kData, sizeof(kData)}; | |
| EXPECT_FALSE(avifPeekCompatibleFileType(&input)); | |
| } | |
| // From https://crbug.com/334682511. | |
| TEST(AvifDecodeTest, PeekCompatibleFileTypeBad2) { | |
| constexpr uint8_t kData[] = {0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, | |
| 0x70, 0x61, 0x73, 0x31, 0x6d, 0x00, 0x00, | |
| 0x08, 0x00, 0xd7, 0x89, 0xdb, 0x7f}; | |
| avifROData input = {kData, sizeof(kData)}; | |
| EXPECT_FALSE(avifPeekCompatibleFileType(&input)); | |
| } | |
| struct NonPersistentIO { | |
| std::vector<uint8_t> data; | |
| avifROData ro_data; | |
| }; | |
| // Every read call will explicitly invalidate the pointer returned by the | |
| // previous read call to ensure non-persistent IO. | |
| avifResult NonPersistentRead(struct avifIO* io, uint32_t flags, uint64_t offset, | |
| size_t size, avifROData* out) { | |
| NonPersistentIO* io_data = reinterpret_cast<NonPersistentIO*>(io->data); | |
| if (flags != 0 || offset > io_data->ro_data.size) { | |
| return AVIF_RESULT_IO_ERROR; | |
| } | |
| uint64_t available_size = io_data->ro_data.size - offset; | |
| if (size > available_size) { | |
| size = static_cast<size_t>(available_size); | |
| } | |
| // Clear the existing vector. | |
| std::vector<uint8_t>().swap(io_data->data); | |
| // Copy new data into the vector. | |
| io_data->data.reserve(size); | |
| io_data->data.assign(io_data->ro_data.data + offset, | |
| io_data->ro_data.data + offset + size); | |
| // Set the output. | |
| out->data = io_data->data.data(); | |
| out->size = size; | |
| return AVIF_RESULT_OK; | |
| } | |
| TEST(AvifDecodeTest, NonPersistentIOBug506387278) { | |
| const testutil::AvifRwData avif = | |
| testutil::ReadFile(std::string(data_path) + "poc_b_506387278.avif"); | |
| NonPersistentIO io_data; | |
| io_data.ro_data = {/*.data=*/avif.data, /*.size=*/avif.size}; | |
| avifIO io = {/*.destroy=*/nullptr, | |
| /*.read=*/NonPersistentRead, | |
| /*.write=*/nullptr, | |
| /*.sizeHint=*/avif.size, | |
| /*.persistent=*/false, | |
| /*.data=*/&io_data}; | |
| // |io| must outlive the decoder. | |
| DecoderPtr decoder(avifDecoderCreate()); | |
| ASSERT_NE(decoder, nullptr); | |
| avifDecoderSetIO(decoder.get(), &io); | |
| ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_EQ(decoder->imageSequenceTrackPresent, AVIF_TRUE); | |
| EXPECT_EQ(decoder->imageCount, 2); | |
| if (testutil::Av1DecoderAvailable()) { | |
| EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); | |
| EXPECT_EQ(avifDecoderNextImage(decoder.get()), AVIF_RESULT_OK); | |
| } | |
| } | |
| } // 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(); | |
| } |