From 55aab4ac0607ab651055d354d64c4615cf3d8000 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Date: Mon, 2 May 2022 14:56:43 -0700 Subject: [PATCH] Make avifImageScale function public This is useful for apps to scale images (without having to worry about bitdepth, subsampling, etc). This commit renames avifImageScale to avifImageScaleWithLimit. The latter would remain an internal only function while the former is now part of the public ABI (moved to avif.h) Potential workaround for issue #1484. --- CHANGELOG.md | 1 + include/avif/avif.h | 7 ++++++ include/avif/internal.h | 14 ++++++------ src/read.c | 14 ++++++------ src/scale.c | 42 ++++++++++++++++++++---------------- tests/gtest/avifscaletest.cc | 32 +++++++++++++++++++-------- 6 files changed, 69 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f44d76d8b..b19b5408b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This part of AV1 encoder is not as thoroughly tested, so there are higher possibility encoder may crash when given certain configuration or input. * Add imageSequenceTrackPresent flag to the avifDecoder struct. +* avifImageScale() function was made part of the public ABI. ### Changed * Update aom.cmd: v3.7.0 diff --git a/include/avif/avif.h b/include/avif/avif.h index c5076ce1eb..0403997814 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -749,6 +749,13 @@ AVIF_API void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, a // the number of threads that can ever be in flight at a given time, please account for this // accordingly. +// --------------------------------------------------------------------------- +// Scaling + +// Scales the YUV/A planes in-place. dstWidth and dstHeight must both be <= AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT and +// dstWidth*dstHeight should be <= AVIF_DEFAULT_IMAGE_SIZE_LIMIT. +AVIF_API avifResult avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag); + // --------------------------------------------------------------------------- // Optional YUV<->RGB support diff --git a/include/avif/internal.h b/include/avif/internal.h index af490e9419..59db4e9aa6 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -251,13 +251,13 @@ void avifSetTileConfiguration(int threads, uint32_t width, uint32_t height, int // --------------------------------------------------------------------------- // Scaling -// This scales the YUV/A planes in-place. -avifBool avifImageScale(avifImage * image, - uint32_t dstWidth, - uint32_t dstHeight, - uint32_t imageSizeLimit, - uint32_t imageDimensionLimit, - avifDiagnostics * diag); +// Scales the YUV/A planes in-place. +avifResult avifImageScaleWithLimit(avifImage * image, + uint32_t dstWidth, + uint32_t dstHeight, + uint32_t imageSizeLimit, + uint32_t imageDimensionLimit, + avifDiagnostics * diag); // --------------------------------------------------------------------------- // AVIF item category diff --git a/src/read.c b/src/read.c index 89182399e4..9eb3b434e1 100644 --- a/src/read.c +++ b/src/read.c @@ -4980,13 +4980,13 @@ static avifResult avifDecoderDecodeTiles(avifDecoder * decoder, uint32_t nextIma // Scale the decoded image so that it corresponds to this tile's output dimensions if ((tile->width != tile->image->width) || (tile->height != tile->image->height)) { - if (!avifImageScale(tile->image, - tile->width, - tile->height, - decoder->imageSizeLimit, - decoder->imageDimensionLimit, - &decoder->diag)) { - avifDiagnosticsPrintf(&decoder->diag, "avifImageScale() failed"); + if (avifImageScaleWithLimit(tile->image, + tile->width, + tile->height, + decoder->imageSizeLimit, + decoder->imageDimensionLimit, + &decoder->diag) != AVIF_RESULT_OK) { + avifDiagnosticsPrintf(&decoder->diag, "avifImageScaleWithLimit() failed"); return avifGetErrorForItemCategory(tile->input->itemCategory); } } diff --git a/src/scale.c b/src/scale.c index fdd8d528a2..52840a5383 100644 --- a/src/scale.c +++ b/src/scale.c @@ -21,25 +21,25 @@ // This should be configurable and/or smarter. kFilterBox has the highest quality but is the slowest. #define AVIF_LIBYUV_FILTER_MODE kFilterBox -avifBool avifImageScale(avifImage * image, - uint32_t dstWidth, - uint32_t dstHeight, - uint32_t imageSizeLimit, - uint32_t imageDimensionLimit, - avifDiagnostics * diag) +avifResult avifImageScaleWithLimit(avifImage * image, + uint32_t dstWidth, + uint32_t dstHeight, + uint32_t imageSizeLimit, + uint32_t imageDimensionLimit, + avifDiagnostics * diag) { if ((image->width == dstWidth) && (image->height == dstHeight)) { // Nothing to do - return AVIF_TRUE; + return AVIF_RESULT_OK; } if ((dstWidth == 0) || (dstHeight == 0)) { - avifDiagnosticsPrintf(diag, "avifImageScale requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight); - return AVIF_FALSE; + avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight); + return AVIF_RESULT_INVALID_ARGUMENT; } if (avifDimensionsTooLarge(dstWidth, dstHeight, imageSizeLimit, imageDimensionLimit)) { - avifDiagnosticsPrintf(diag, "avifImageScale requested dst dimensions that are too large [%ux%u]", dstWidth, dstHeight); - return AVIF_FALSE; + avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested dst dimensions that are too large [%ux%u]", dstWidth, dstHeight); + return AVIF_RESULT_NOT_IMPLEMENTED; } uint8_t * srcYUVPlanes[AVIF_PLANE_COUNT_YUV]; @@ -71,12 +71,12 @@ avifBool avifImageScale(avifImage * image, // A simple conservative check to avoid integer overflows in libyuv's ScalePlane() and // ScalePlane_12() functions. if (srcWidth > 16384) { - avifDiagnosticsPrintf(diag, "avifImageScale requested invalid width scale for libyuv [%u -> %u]", srcWidth, dstWidth); - return AVIF_FALSE; + avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid width scale for libyuv [%u -> %u]", srcWidth, dstWidth); + return AVIF_RESULT_NOT_IMPLEMENTED; } if (srcHeight > 16384) { - avifDiagnosticsPrintf(diag, "avifImageScale requested invalid height scale for libyuv [%u -> %u]", srcHeight, dstHeight); - return AVIF_FALSE; + avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid height scale for libyuv [%u -> %u]", srcHeight, dstHeight); + return AVIF_RESULT_NOT_IMPLEMENTED; } } @@ -84,7 +84,7 @@ avifBool avifImageScale(avifImage * image, const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_YUV); if (allocationResult != AVIF_RESULT_OK) { avifDiagnosticsPrintf(diag, "Allocation of YUV planes failed: %s", avifResultToString(allocationResult)); - return AVIF_FALSE; + return AVIF_RESULT_OUT_OF_MEMORY; } for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) { @@ -124,7 +124,7 @@ avifBool avifImageScale(avifImage * image, const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_A); if (allocationResult != AVIF_RESULT_OK) { avifDiagnosticsPrintf(diag, "Allocation of alpha plane failed: %s", avifResultToString(allocationResult)); - return AVIF_FALSE; + return AVIF_RESULT_OUT_OF_MEMORY; } if (image->depth > 8) { @@ -150,5 +150,11 @@ avifBool avifImageScale(avifImage * image, } } - return AVIF_TRUE; + return AVIF_RESULT_OK; +} + +avifResult avifImageScale(avifImage * image, uint32_t dstWidth, uint32_t dstHeight, avifDiagnostics * diag) +{ + avifDiagnosticsClearError(diag); + return avifImageScaleWithLimit(image, dstWidth, dstHeight, AVIF_DEFAULT_IMAGE_SIZE_LIMIT, AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT, diag); } diff --git a/tests/gtest/avifscaletest.cc b/tests/gtest/avifscaletest.cc index 26bf5fe82d..652e36a601 100644 --- a/tests/gtest/avifscaletest.cc +++ b/tests/gtest/avifscaletest.cc @@ -18,6 +18,8 @@ namespace { // Used to pass the data folder path to the GoogleTest suites. const char* data_path = nullptr; +constexpr bool kIgnoreMetadata = true; + //------------------------------------------------------------------------------ class ScaleTest @@ -30,7 +32,6 @@ TEST_P(ScaleTest, Roundtrip) { const avifPixelFormat yuv_format = std::get<1>(GetParam()); const bool create_alpha = std::get<2>(GetParam()); - const bool kIgnoreMetadata = true; const testutil::AvifImagePtr image = testutil::ReadImage(data_path, "paris_exif_xmp_icc.jpg", yuv_format, bit_depth, AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY, @@ -50,19 +51,17 @@ TEST_P(ScaleTest, Roundtrip) { const uint32_t scaled_width = static_cast(image->width * 0.9); const uint32_t scaled_height = static_cast(image->height * 2.14); - const uint32_t kImageSizeLimit = std::numeric_limits::max(); - const uint32_t kImageDimensionLimit = std::numeric_limits::max(); avifDiagnostics diag; - avifDiagnosticsClearError(&diag); - ASSERT_TRUE(avifImageScale(scaled_image.get(), scaled_width, scaled_height, - kImageSizeLimit, kImageDimensionLimit, &diag)) + ASSERT_EQ( + avifImageScale(scaled_image.get(), scaled_width, scaled_height, &diag), + AVIF_RESULT_OK) << diag.error; EXPECT_EQ(scaled_image->width, scaled_width); EXPECT_EQ(scaled_image->height, scaled_height); - avifDiagnosticsClearError(&diag); - ASSERT_TRUE(avifImageScale(scaled_image.get(), image->width, image->height, - kImageSizeLimit, kImageDimensionLimit, &diag)) + ASSERT_EQ( + avifImageScale(scaled_image.get(), image->width, image->height, &diag), + AVIF_RESULT_OK) << diag.error; EXPECT_EQ(scaled_image->width, image->width); EXPECT_EQ(scaled_image->height, image->height); @@ -79,6 +78,21 @@ INSTANTIATE_TEST_SUITE_P( AVIF_PIXEL_FORMAT_YUV420, AVIF_PIXEL_FORMAT_YUV400), /*create_alpha=*/Values(true, false))); +TEST(ScaleTest, LargerThanDefaultLimits) { + const testutil::AvifImagePtr image = testutil::ReadImage( + data_path, "paris_exif_xmp_icc.jpg", AVIF_PIXEL_FORMAT_YUV420, 8, + AVIF_CHROMA_DOWNSAMPLING_BEST_QUALITY, kIgnoreMetadata, kIgnoreMetadata, + kIgnoreMetadata); + ASSERT_NE(image, nullptr); + avifDiagnostics diag; + // dstWidth*dstHeight is larger than AVIF_DEFAULT_IMAGE_SIZE_LIMIT. + EXPECT_NE(avifImageScale(image.get(), 20000, 20000, &diag), AVIF_RESULT_OK); + // dstWidth is larger than AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT. + EXPECT_NE(avifImageScale(image.get(), 40000, 2, &diag), AVIF_RESULT_OK); + // dstHeight is larger than AVIF_DEFAULT_IMAGE_DIMENSION_LIMIT. + EXPECT_NE(avifImageScale(image.get(), 2, 40000, &diag), AVIF_RESULT_OK); +} + //------------------------------------------------------------------------------ } // namespace