| // Copyright 2022 Google LLC | |
| // SPDX-License-Identifier: BSD-2-Clause | |
| #include <limits> | |
| #include <vector> | |
| #include "avif/avif.h" | |
| #include "aviftest_helpers.h" | |
| #include "gtest/gtest.h" | |
| namespace avif { | |
| namespace { | |
| void TestAllocation(uint32_t width, uint32_t height, uint32_t depth, | |
| avifResult expected_result) { | |
| // The format of the image and which planes are allocated should not matter. | |
| // Test all combinations. | |
| for (avifPixelFormat format : | |
| {AVIF_PIXEL_FORMAT_NONE, AVIF_PIXEL_FORMAT_YUV444, | |
| AVIF_PIXEL_FORMAT_YUV422, AVIF_PIXEL_FORMAT_YUV420, | |
| AVIF_PIXEL_FORMAT_YUV400}) { | |
| for (avifPlanesFlag planes : | |
| {AVIF_PLANES_YUV, AVIF_PLANES_A, AVIF_PLANES_ALL}) { | |
| ImagePtr image(avifImageCreateEmpty()); | |
| ASSERT_NE(image, nullptr); | |
| image->width = width; | |
| image->height = height; | |
| image->depth = depth; | |
| image->yuvFormat = format; | |
| EXPECT_EQ(avifImageAllocatePlanes(image.get(), planes), expected_result); | |
| // Make sure the actual plane pointers are consistent with the settings. | |
| if (expected_result == AVIF_RESULT_OK && | |
| format != AVIF_PIXEL_FORMAT_NONE && (planes & AVIF_PLANES_YUV)) { | |
| EXPECT_NE(image->yuvPlanes[AVIF_CHAN_Y], nullptr); | |
| } else { | |
| EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_Y], nullptr); | |
| } | |
| if (expected_result == AVIF_RESULT_OK && | |
| format != AVIF_PIXEL_FORMAT_NONE && | |
| format != AVIF_PIXEL_FORMAT_YUV400 && (planes & AVIF_PLANES_YUV)) { | |
| EXPECT_NE(image->yuvPlanes[AVIF_CHAN_U], nullptr); | |
| EXPECT_NE(image->yuvPlanes[AVIF_CHAN_V], nullptr); | |
| } else { | |
| EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_U], nullptr); | |
| EXPECT_EQ(image->yuvPlanes[AVIF_CHAN_V], nullptr); | |
| } | |
| if (expected_result == AVIF_RESULT_OK && (planes & AVIF_PLANES_A)) { | |
| EXPECT_NE(image->alphaPlane, nullptr); | |
| } else { | |
| EXPECT_EQ(image->alphaPlane, nullptr); | |
| } | |
| } | |
| } | |
| } | |
| TEST(AllocationTest, MinimumValidDimensions) { | |
| TestAllocation(1, 1, 8, AVIF_RESULT_OK); | |
| } | |
| // Up to SIZE_MAX can be passed as the input argument to avifAlloc(). Testing | |
| // this value is unrealistic so allocate 1 GB: that should pass on all platforms | |
| // and environments. | |
| TEST(AllocationTest, Allocate1GB) { | |
| // 8 bits, so one byte per pixel per channel, up to 4 channels | |
| TestAllocation((1 << 30) / 4, 1, 8, AVIF_RESULT_OK); | |
| TestAllocation(1, (1 << 30) / 4, 8, AVIF_RESULT_OK); | |
| // 12 bits, so two bytes per pixel per channel, up to 4 channels | |
| TestAllocation((1 << 30) / 2 / 4, 1, 12, AVIF_RESULT_OK); | |
| TestAllocation(1, (1 << 30) / 2 / 4, 12, AVIF_RESULT_OK); | |
| TestAllocation(1 << 15, (1 << 15) / 2 / 4, 12, AVIF_RESULT_OK); | |
| } | |
| TEST(AllocationTest, MinimumInvalidDimensions) { | |
| TestAllocation(std::numeric_limits<decltype(avifImage::width)>::max(), 1, 12, | |
| AVIF_RESULT_INVALID_ARGUMENT); | |
| } | |
| TEST(AllocationTest, MaximumInvalidDimensions) { | |
| TestAllocation(std::numeric_limits<decltype(avifImage::width)>::max(), | |
| std::numeric_limits<decltype(avifImage::height)>::max(), 12, | |
| AVIF_RESULT_INVALID_ARGUMENT); | |
| } | |
| TEST(DISABLED_AllocationTest, OutOfMemory) { | |
| // This should pass on 64-bit but may fail on 32-bit or other setups. | |
| TestAllocation(std::numeric_limits<decltype(avifImage::width)>::max(), 1, 8, | |
| AVIF_RESULT_OK); | |
| // This is valid in theory: malloc() should always refuse to allocate so much, | |
| // but avifAlloc() aborts on malloc() failure instead of returning. | |
| TestAllocation(std::numeric_limits<decltype(avifImage::width)>::max() / 2, | |
| std::numeric_limits<decltype(avifImage::height)>::max(), 12, | |
| AVIF_RESULT_OUT_OF_MEMORY); | |
| } | |
| void TestEncoding(uint32_t width, uint32_t height, uint32_t depth, | |
| avifResult expected_result) { | |
| ImagePtr image(avifImageCreateEmpty()); | |
| ASSERT_NE(image, nullptr); | |
| image->width = width; | |
| image->height = height; | |
| image->depth = depth; | |
| image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444; | |
| // This is a fairly high number of bytes that can safely be allocated in this | |
| // test. The goal is to have something to give to libavif but libavif should | |
| // return an error before attempting to read all of it, so it does not matter | |
| // if there are fewer bytes than the provided image dimensions. | |
| static constexpr uint64_t kMaxAlloc = 1073741824; | |
| uint32_t row_bytes; | |
| size_t num_allocated_bytes; | |
| if ((uint64_t)image->width * image->height > | |
| kMaxAlloc / (avifImageUsesU16(image.get()) ? 2 : 1)) { | |
| row_bytes = 1024; // Does not matter much. | |
| num_allocated_bytes = kMaxAlloc; | |
| } else { | |
| row_bytes = image->width * (avifImageUsesU16(image.get()) ? 2 : 1); | |
| num_allocated_bytes = row_bytes * image->height; | |
| } | |
| // Initialize pixels as 16b values to make sure values are valid for 10 | |
| // and 12-bit depths. The array will be cast to uint8_t for 8-bit depth. | |
| std::vector<uint16_t> pixels(num_allocated_bytes / sizeof(uint16_t), 400); | |
| uint8_t* bytes = reinterpret_cast<uint8_t*>(pixels.data()); | |
| // Avoid avifImageAllocatePlanes() to exercise the checks at encoding. | |
| image->imageOwnsYUVPlanes = AVIF_FALSE; | |
| image->imageOwnsAlphaPlane = AVIF_FALSE; | |
| image->yuvRowBytes[AVIF_CHAN_Y] = row_bytes; | |
| image->yuvPlanes[AVIF_CHAN_Y] = bytes; | |
| image->yuvRowBytes[AVIF_CHAN_U] = row_bytes; | |
| image->yuvPlanes[AVIF_CHAN_U] = bytes; | |
| image->yuvRowBytes[AVIF_CHAN_V] = row_bytes; | |
| image->yuvPlanes[AVIF_CHAN_V] = bytes; | |
| image->alphaRowBytes = row_bytes; | |
| image->alphaPlane = bytes; | |
| // Try to encode. | |
| EncoderPtr encoder(avifEncoderCreate()); | |
| ASSERT_NE(encoder, nullptr); | |
| encoder->speed = AVIF_SPEED_FASTEST; | |
| testutil::AvifRwData encoded_avif; | |
| ASSERT_EQ(avifEncoderWrite(encoder.get(), image.get(), &encoded_avif), | |
| expected_result); | |
| } | |
| TEST(EncodingTest, MinimumValidDimensions) { | |
| TestAllocation(1, 1, 8, AVIF_RESULT_OK); | |
| } | |
| TEST(EncodingTest, ReasonableValidDimensions) { | |
| // 16384 x 8704 is the maximum width x height allowed in the levels defined | |
| // in the AV1 specification. | |
| TestEncoding(16384, 1, 12, AVIF_RESULT_OK); | |
| TestEncoding(1, 8704, 12, AVIF_RESULT_OK); | |
| } | |
| // 65536 is the maximum AV1 frame dimension allowed by the AV1 specification. | |
| // See the section 5.5.1 General sequence header OBU syntax. | |
| // However, this test is disabled because: | |
| // - Old versions of libaom are capped to 65535 (http://crbug.com/aomedia/3304). | |
| // - libaom may be compiled with CONFIG_SIZE_LIMIT defined, limiting the | |
| // internal allocation to DECODE_WIDTH_LIMIT and DECODE_HEIGHT_LIMIT during | |
| // encoding in aom_realloc_frame_buffer(). | |
| TEST(DISABLED_EncodingTest, MaximumValidDimensions) { | |
| TestEncoding(65536, 1, 12, AVIF_RESULT_OK); | |
| TestEncoding(1, 65536, 12, AVIF_RESULT_OK); | |
| // TestEncoding(65536, 65536, 12, AVIF_RESULT_OK); // Too slow. | |
| } | |
| TEST(EncodingTest, MinimumInvalidDimensions) { | |
| TestEncoding(0, 1, 8, AVIF_RESULT_NO_CONTENT); | |
| TestEncoding(1, 0, 8, AVIF_RESULT_NO_CONTENT); | |
| TestEncoding(1, 1, 0, AVIF_RESULT_UNSUPPORTED_DEPTH); | |
| TestEncoding(65536 + 1, 1, 8, AVIF_RESULT_INVALID_ARGUMENT); | |
| TestEncoding(1, 65536 + 1, 8, AVIF_RESULT_INVALID_ARGUMENT); | |
| TestEncoding(65536 + 1, 65536 + 1, 8, AVIF_RESULT_INVALID_ARGUMENT); | |
| } | |
| TEST(EncodingTest, MaximumInvalidDimensions) { | |
| TestEncoding(std::numeric_limits<decltype(avifImage::width)>::max(), 1, 8, | |
| AVIF_RESULT_INVALID_ARGUMENT); | |
| TestEncoding(1, std::numeric_limits<decltype(avifImage::height)>::max(), 8, | |
| AVIF_RESULT_INVALID_ARGUMENT); | |
| TestEncoding(std::numeric_limits<decltype(avifImage::width)>::max(), | |
| std::numeric_limits<decltype(avifImage::height)>::max(), 12, | |
| AVIF_RESULT_INVALID_ARGUMENT); | |
| TestEncoding(1, 1, std::numeric_limits<decltype(avifImage::depth)>::max(), | |
| AVIF_RESULT_UNSUPPORTED_DEPTH); | |
| } | |
| #if defined(__has_feature) | |
| #if __has_feature(memory_sanitizer) | |
| #define TEST_EXTREMES 0 | |
| #else | |
| #define TEST_EXTREMES 1 | |
| #endif | |
| #else | |
| #define TEST_EXTREMES 1 | |
| #endif | |
| #if TEST_EXTREMES | |
| TEST(AvifAllocTest, Extremes) { | |
| void* p1 = avifAlloc(1); | |
| EXPECT_NE(p1, nullptr); | |
| avifFree(p1); | |
| EXPECT_EQ(avifAlloc(std::numeric_limits<size_t>::max()), nullptr); | |
| } | |
| #endif | |
| TEST(AvifAllocTest, ZeroReturnsNull) { | |
| // avifAlloc(0) must return NULL deterministically (rather than aborting via | |
| // assert or relying on implementation-defined malloc(0) behavior). Callers | |
| // rely on this contract to treat 0 as an allocation failure. | |
| EXPECT_EQ(avifAlloc(0), nullptr); | |
| } | |
| TEST(AvifAllocTest, RWDataReallocShrinkToZeroSucceeds) { | |
| // Shrinking an avifRWData to size 0 must release the buffer and return OK, | |
| // not surface a phantom OOM from avifAlloc(0) returning NULL. | |
| avifRWData raw = {}; | |
| ASSERT_EQ(avifRWDataRealloc(&raw, 16), AVIF_RESULT_OK); | |
| ASSERT_NE(raw.data, nullptr); | |
| ASSERT_EQ(raw.size, 16u); | |
| EXPECT_EQ(avifRWDataRealloc(&raw, 0), AVIF_RESULT_OK); | |
| EXPECT_EQ(raw.data, nullptr); | |
| EXPECT_EQ(raw.size, 0u); | |
| avifRWDataFree(&raw); | |
| } | |
| } // namespace | |
| } // namespace avif |