| // Copyright 2021 Joe Drago. All rights reserved. | |
| // SPDX-License-Identifier: BSD-2-Clause | |
| #include "avif/internal.h" | |
| #include <limits.h> | |
| #if defined(__clang__) | |
| #pragma clang diagnostic push | |
| #pragma clang diagnostic ignored "-Wstrict-prototypes" // "this function declaration is not a prototype" | |
| // The newline at the end of libyuv/version.h was accidentally deleted in version 1792 and restored | |
| // in version 1813: | |
| // https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3183182 | |
| // https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3527834 | |
| #pragma clang diagnostic ignored "-Wnewline-eof" // "no newline at end of file" | |
| #endif | |
| #include <libyuv.h> | |
| #if defined(__clang__) | |
| #pragma clang diagnostic pop | |
| #endif | |
| // This should be configurable and/or smarter. kFilterBox has the highest quality but is the slowest. | |
| #define AVIF_LIBYUV_FILTER_MODE kFilterBox | |
| 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_RESULT_OK; | |
| } | |
| if ((dstWidth == 0) || (dstHeight == 0)) { | |
| avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid dst dimensions [%ux%u]", dstWidth, dstHeight); | |
| return AVIF_RESULT_INVALID_ARGUMENT; | |
| } | |
| if (avifDimensionsTooLarge(dstWidth, dstHeight, imageSizeLimit, imageDimensionLimit)) { | |
| 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]; | |
| uint32_t srcYUVRowBytes[AVIF_PLANE_COUNT_YUV]; | |
| for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) { | |
| srcYUVPlanes[i] = image->yuvPlanes[i]; | |
| image->yuvPlanes[i] = NULL; | |
| srcYUVRowBytes[i] = image->yuvRowBytes[i]; | |
| image->yuvRowBytes[i] = 0; | |
| } | |
| const avifBool srcImageOwnsYUVPlanes = image->imageOwnsYUVPlanes; | |
| image->imageOwnsYUVPlanes = AVIF_FALSE; | |
| uint8_t * srcAlphaPlane = image->alphaPlane; | |
| image->alphaPlane = NULL; | |
| uint32_t srcAlphaRowBytes = image->alphaRowBytes; | |
| image->alphaRowBytes = 0; | |
| const avifBool srcImageOwnsAlphaPlane = image->imageOwnsAlphaPlane; | |
| image->imageOwnsAlphaPlane = AVIF_FALSE; | |
| const uint32_t srcWidth = image->width; | |
| const uint32_t srcHeight = image->height; | |
| const uint32_t srcUVWidth = avifImagePlaneWidth(image, AVIF_CHAN_U); | |
| const uint32_t srcUVHeight = avifImagePlaneHeight(image, AVIF_CHAN_U); | |
| image->width = dstWidth; | |
| image->height = dstHeight; | |
| avifResult result = AVIF_RESULT_OK; | |
| if (srcYUVPlanes[0] || srcAlphaPlane) { | |
| // A simple conservative check to avoid integer overflows in libyuv's ScalePlane() and | |
| // ScalePlane_12() functions. | |
| if (srcWidth > 16384) { | |
| avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid width scale for libyuv [%u -> %u]", srcWidth, dstWidth); | |
| result = AVIF_RESULT_NOT_IMPLEMENTED; | |
| goto cleanup; | |
| } | |
| if (srcHeight > 16384) { | |
| avifDiagnosticsPrintf(diag, "avifImageScaleWithLimit requested invalid height scale for libyuv [%u -> %u]", srcHeight, dstHeight); | |
| result = AVIF_RESULT_NOT_IMPLEMENTED; | |
| goto cleanup; | |
| } | |
| } | |
| if (srcYUVPlanes[0]) { | |
| const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_YUV); | |
| if (allocationResult != AVIF_RESULT_OK) { | |
| avifDiagnosticsPrintf(diag, "Allocation of YUV planes failed: %s", avifResultToString(allocationResult)); | |
| result = AVIF_RESULT_OUT_OF_MEMORY; | |
| goto cleanup; | |
| } | |
| for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) { | |
| if (!srcYUVPlanes[i]) { | |
| continue; | |
| } | |
| const uint32_t srcW = (i == AVIF_CHAN_Y) ? srcWidth : srcUVWidth; | |
| const uint32_t srcH = (i == AVIF_CHAN_Y) ? srcHeight : srcUVHeight; | |
| const uint32_t dstW = avifImagePlaneWidth(image, i); | |
| const uint32_t dstH = avifImagePlaneHeight(image, i); | |
| if (image->depth > 8) { | |
| uint16_t * const srcPlane = (uint16_t *)srcYUVPlanes[i]; | |
| const uint32_t srcStride = srcYUVRowBytes[i] / 2; | |
| uint16_t * const dstPlane = (uint16_t *)image->yuvPlanes[i]; | |
| const uint32_t dstStride = image->yuvRowBytes[i] / 2; | |
| #if LIBYUV_VERSION >= 1880 | |
| const int failure = | |
| ScalePlane_12(srcPlane, srcStride, srcW, srcH, dstPlane, dstStride, dstW, dstH, AVIF_LIBYUV_FILTER_MODE); | |
| if (failure) { | |
| avifDiagnosticsPrintf(diag, "ScalePlane_12() failed (%d)", failure); | |
| result = (failure == 1) ? AVIF_RESULT_OUT_OF_MEMORY : AVIF_RESULT_UNKNOWN_ERROR; | |
| goto cleanup; | |
| } | |
| #elif LIBYUV_VERSION >= 1774 | |
| ScalePlane_12(srcPlane, srcStride, srcW, srcH, dstPlane, dstStride, dstW, dstH, AVIF_LIBYUV_FILTER_MODE); | |
| #else | |
| ScalePlane_16(srcPlane, srcStride, srcW, srcH, dstPlane, dstStride, dstW, dstH, AVIF_LIBYUV_FILTER_MODE); | |
| #endif | |
| } else { | |
| uint8_t * const srcPlane = srcYUVPlanes[i]; | |
| const uint32_t srcStride = srcYUVRowBytes[i]; | |
| uint8_t * const dstPlane = image->yuvPlanes[i]; | |
| const uint32_t dstStride = image->yuvRowBytes[i]; | |
| #if LIBYUV_VERSION >= 1880 | |
| const int failure = ScalePlane(srcPlane, srcStride, srcW, srcH, dstPlane, dstStride, dstW, dstH, AVIF_LIBYUV_FILTER_MODE); | |
| if (failure) { | |
| avifDiagnosticsPrintf(diag, "ScalePlane() failed (%d)", failure); | |
| result = (failure == 1) ? AVIF_RESULT_OUT_OF_MEMORY : AVIF_RESULT_UNKNOWN_ERROR; | |
| goto cleanup; | |
| } | |
| #else | |
| ScalePlane(srcPlane, srcStride, srcW, srcH, dstPlane, dstStride, dstW, dstH, AVIF_LIBYUV_FILTER_MODE); | |
| #endif | |
| } | |
| } | |
| } | |
| if (srcAlphaPlane) { | |
| const avifResult allocationResult = avifImageAllocatePlanes(image, AVIF_PLANES_A); | |
| if (allocationResult != AVIF_RESULT_OK) { | |
| avifDiagnosticsPrintf(diag, "Allocation of alpha plane failed: %s", avifResultToString(allocationResult)); | |
| result = AVIF_RESULT_OUT_OF_MEMORY; | |
| goto cleanup; | |
| } | |
| if (image->depth > 8) { | |
| uint16_t * const srcPlane = (uint16_t *)srcAlphaPlane; | |
| const uint32_t srcStride = srcAlphaRowBytes / 2; | |
| uint16_t * const dstPlane = (uint16_t *)image->alphaPlane; | |
| const uint32_t dstStride = image->alphaRowBytes / 2; | |
| #if LIBYUV_VERSION >= 1880 | |
| const int failure = | |
| ScalePlane_12(srcPlane, srcStride, srcWidth, srcHeight, dstPlane, dstStride, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE); | |
| if (failure) { | |
| avifDiagnosticsPrintf(diag, "ScalePlane_12() failed (%d)", failure); | |
| result = (failure == 1) ? AVIF_RESULT_OUT_OF_MEMORY : AVIF_RESULT_UNKNOWN_ERROR; | |
| goto cleanup; | |
| } | |
| #elif LIBYUV_VERSION >= 1774 | |
| ScalePlane_12(srcPlane, srcStride, srcWidth, srcHeight, dstPlane, dstStride, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE); | |
| #else | |
| ScalePlane_16(srcPlane, srcStride, srcWidth, srcHeight, dstPlane, dstStride, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE); | |
| #endif | |
| } else { | |
| uint8_t * const srcPlane = srcAlphaPlane; | |
| const uint32_t srcStride = srcAlphaRowBytes; | |
| uint8_t * const dstPlane = image->alphaPlane; | |
| const uint32_t dstStride = image->alphaRowBytes; | |
| #if LIBYUV_VERSION >= 1880 | |
| const int failure = | |
| ScalePlane(srcPlane, srcStride, srcWidth, srcHeight, dstPlane, dstStride, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE); | |
| if (failure) { | |
| avifDiagnosticsPrintf(diag, "ScalePlane() failed (%d)", failure); | |
| result = (failure == 1) ? AVIF_RESULT_OUT_OF_MEMORY : AVIF_RESULT_UNKNOWN_ERROR; | |
| goto cleanup; | |
| } | |
| #else | |
| ScalePlane(srcPlane, srcStride, srcWidth, srcHeight, dstPlane, dstStride, dstWidth, dstHeight, AVIF_LIBYUV_FILTER_MODE); | |
| #endif | |
| } | |
| } | |
| cleanup: | |
| if (srcYUVPlanes[0] && srcImageOwnsYUVPlanes) { | |
| for (int i = 0; i < AVIF_PLANE_COUNT_YUV; ++i) { | |
| avifFree(srcYUVPlanes[i]); | |
| } | |
| } | |
| if (srcAlphaPlane && srcImageOwnsAlphaPlane) { | |
| avifFree(srcAlphaPlane); | |
| } | |
| return result; | |
| } | |
| 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); | |
| } |