// Copyright 2019 Joe Drago. All rights reserved.
// SPDX-License-Identifier: BSD-2-Clause

#include "avif/internal.h"

#if !defined(AVIF_LIBYUV_ENABLED)

// No libyuv!
avifResult avifImageRGBToYUVLibYUV(avifImage * image, const avifRGBImage * rgb)
{
    (void)image;
    (void)rgb;
    return AVIF_RESULT_NOT_IMPLEMENTED;
}
avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifBool reformatAlpha, avifBool * alphaReformattedWithLibYUV)
{
    (void)image;
    (void)rgb;
    (void)reformatAlpha;
    *alphaReformattedWithLibYUV = AVIF_FALSE;
    return AVIF_RESULT_NOT_IMPLEMENTED;
}
avifResult avifRGBImagePremultiplyAlphaLibYUV(avifRGBImage * rgb)
{
    (void)rgb;
    return AVIF_RESULT_NOT_IMPLEMENTED;
}
avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb)
{
    (void)rgb;
    return AVIF_RESULT_NOT_IMPLEMENTED;
}
avifResult avifRGBImageToF16LibYUV(avifRGBImage * rgb)
{
    (void)rgb;
    return AVIF_RESULT_NOT_IMPLEMENTED;
}
unsigned int avifLibYUVVersion(void)
{
    return 0;
}

#else

#include <assert.h>
#include <limits.h>
#include <string.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

// libyuv is a C++ library and defines custom types (struct, enum, etc) in the libyuv namespace when the libyuv header files are
// included by C++ code. When accessed from a C library like libavif, via a function pointer, this leads to signature mismatches
// in the CFI sanitizers since libyuv itself, compiled as C++ code, has the types within the namespace and the C code has the
// types without the namespace. The same thing happens with clang's undefined behavior sanitizer as well when invoked with
// -fsanitize=function. So we suppress both of these sanitizers in functions that call libyuv functions via a pointer.
// For a simpler example of this bug, please see: https://github.com/vigneshvg/cpp_c_potential_cfi_bug.
// For more details on clang's CFI see: https://clang.llvm.org/docs/ControlFlowIntegrity.html.
// For more details on clang's UBSan see: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
#if defined(__clang__)
#define IGNORE_CFI_ICALL __attribute__((no_sanitize("cfi-icall", "function")))
#else
#define IGNORE_CFI_ICALL
#endif

//--------------------------------------------------------------------------------------------------
// libyuv API availability management

// These defines are used to create a NULL reference to libyuv functions that
// did not exist prior to a particular version of libyuv.
// Versions prior to 1755 are considered too old and not used (see CMakeLists.txt).
#if LIBYUV_VERSION < 1902
// RAWToJ444 was added in libyuv version 1902.
// See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/6207845.
// NEON was added afterward, but the version was not increased.
// See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/6220890
#define RAWToJ444 NULL
#endif
#if LIBYUV_VERSION < 1844
// I444ToRGB24Matrix() and I422ToRGB24MatrixFilter() were added in libyuv version 1844.
//
// Note: Between the following two commits, libyuv version jumped from 1841 to 1844, down to 1843,
// and back to 1844. See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3906082 and
// https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3906091.
#define I444ToRGB24Matrix NULL
#define I422ToRGB24MatrixFilter NULL
#endif
#if LIBYUV_VERSION < 1841
// I420ToRGB24MatrixFilter() was added in libyuv version 1841.
// See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3900298.
#define I420ToRGB24MatrixFilter NULL
#endif
#if LIBYUV_VERSION < 1840
#define ABGRToJ400 NULL
#endif
#if LIBYUV_VERSION < 1838
#define I422ToRGB565Matrix NULL
#endif
#if LIBYUV_VERSION < 1813
#define I422ToARGBMatrixFilter NULL
#define I420ToARGBMatrixFilter NULL
#define I210ToARGBMatrixFilter NULL
#define I010ToARGBMatrixFilter NULL
#define I420AlphaToARGBMatrixFilter NULL
#define I422AlphaToARGBMatrixFilter NULL
#define I010AlphaToARGBMatrixFilter NULL
#define I210AlphaToARGBMatrixFilter NULL
#endif
#if LIBYUV_VERSION < 1782
#define RAWToJ420 NULL
#endif
#if LIBYUV_VERSION < 1781
#define I012ToARGBMatrix NULL
#endif
#if LIBYUV_VERSION < 1780
#define I410ToARGBMatrix NULL
#define I410AlphaToARGBMatrix NULL
#define I210AlphaToARGBMatrix NULL
#define I010AlphaToARGBMatrix NULL
#endif
#if LIBYUV_VERSION < 1771
#define I422AlphaToARGBMatrix NULL
#define I444AlphaToARGBMatrix NULL
#endif
#if LIBYUV_VERSION < 1756
#define I400ToARGBMatrix NULL
#endif

// Two-step replacement for the conversions to 8-bit BT.601 YUV which are missing from libyuv.
static int avifReorderARGBThenConvertToYUV(int (*ReorderARGB)(const uint8_t *, int, uint8_t *, int, int, int),
                                           int (*ConvertToYUV)(const uint8_t *, int, uint8_t *, int, uint8_t *, int, uint8_t *, int, int, int),
                                           const uint8_t * src_abgr,
                                           int src_stride_abgr,
                                           uint8_t * dst_y,
                                           int dst_stride_y,
                                           uint8_t * dst_u,
                                           int dst_stride_u,
                                           uint8_t * dst_v,
                                           int dst_stride_v,
                                           avifPixelFormat dst_format,
                                           int width,
                                           int height)
{
    // Only the vertically subsampled formats need to be processed by luma row pairs.
    avifPixelFormatInfo format_info;
    avifGetPixelFormatInfo(dst_format, &format_info);
    const int min_num_rows = (format_info.chromaShiftY == 1) ? 2 : 1;

    // A temporary buffer is needed to call ReorderARGB().
    uint8_t * src_argb;
    const int src_stride_argb = width * 4;
    const int soft_allocation_limit = 16384; // Arbitrarily chosen trade-off between CPU and memory footprints.
    int num_allocated_rows;
    if ((height == 1) || ((int64_t)src_stride_argb * height <= soft_allocation_limit)) {
        // Process the whole buffer in one go.
        num_allocated_rows = height;
    } else {
        if ((int64_t)src_stride_argb * min_num_rows > INT_MAX) {
            return -1;
        }
        // The last row of an odd number of RGB rows to be converted to vertically subsampled YUV is treated
        // differently by libyuv, so make sure all steps but the last one process a multiple of min_num_rows rows.
        // Try to process the highest multiple of min_num_rows rows possible in a single step without
        // allocating more than soft_allocation_limit, unless min_num_rows rows need more than that.
        num_allocated_rows = AVIF_MAX(1, soft_allocation_limit / (src_stride_argb * min_num_rows)) * min_num_rows;
    }
    src_argb = (uint8_t *)avifAlloc(num_allocated_rows * src_stride_argb);
    if (!src_argb) {
        return -1;
    }

    for (int y = 0; y < height; y += num_allocated_rows) {
        const int num_rows = AVIF_MIN(num_allocated_rows, height - y);
        if (ReorderARGB(src_abgr, src_stride_abgr, src_argb, src_stride_argb, width, num_rows) ||
            ConvertToYUV(src_argb, src_stride_argb, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, width, num_rows)) {
            avifFree(src_argb);
            return -1;
        }
        src_abgr += (size_t)num_rows * src_stride_abgr;
        dst_y += (size_t)num_rows * dst_stride_y;
        // Either chroma is not vertically subsampled, num_rows is even, or this is the last iteration.
        dst_u += (size_t)(num_rows >> format_info.chromaShiftY) * dst_stride_u;
        dst_v += (size_t)(num_rows >> format_info.chromaShiftY) * dst_stride_v;
    }
    avifFree(src_argb);
    return 0;
}

#define AVIF_DEFINE_CONVERSION(NAME, REORDER_ARGB, CONVERT_TO_YUV, YUV_FORMAT) \
    static int NAME(const uint8_t * src_abgr,                                  \
                    int src_stride_abgr,                                       \
                    uint8_t * dst_y,                                           \
                    int dst_stride_y,                                          \
                    uint8_t * dst_u,                                           \
                    int dst_stride_u,                                          \
                    uint8_t * dst_v,                                           \
                    int dst_stride_v,                                          \
                    int width,                                                 \
                    int height)                                                \
    {                                                                          \
        return avifReorderARGBThenConvertToYUV(REORDER_ARGB,                   \
                                               CONVERT_TO_YUV,                 \
                                               src_abgr,                       \
                                               src_stride_abgr,                \
                                               dst_y,                          \
                                               dst_stride_y,                   \
                                               dst_u,                          \
                                               dst_stride_u,                   \
                                               dst_v,                          \
                                               dst_stride_v,                   \
                                               YUV_FORMAT,                     \
                                               width,                          \
                                               height);                        \
    }

#if LIBYUV_VERSION < 1903
// AVIF_RGB_FORMAT_RGB
// RAWToI444 was added in libyuv version 1903.
// https://chromium-review.googlesource.com/c/libyuv/libyuv/+/6223658
AVIF_DEFINE_CONVERSION(RAWToI444, RAWToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
#endif
#if LIBYUV_VERSION < 1840
// AVIF_RGB_FORMAT_RGBA
AVIF_DEFINE_CONVERSION(ABGRToJ422, ABGRToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(ABGRToJ420, ABGRToARGB, ARGBToJ420, AVIF_PIXEL_FORMAT_YUV420)
#endif

// These are not yet implemented in libyuv so they cannot be guarded by a version check.
// The "avif" prefix avoids any redefinition if they are available in libyuv one day.
// AVIF_RGB_FORMAT_RGB
AVIF_DEFINE_CONVERSION(avifRAWToI422, RAWToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifRAWToJ422, RAWToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
// AVIF_RGB_FORMAT_RGBA
AVIF_DEFINE_CONVERSION(avifABGRToI444, ABGRToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifABGRToI422, ABGRToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
// AVIF_RGB_FORMAT_ARGB
AVIF_DEFINE_CONVERSION(avifBGRAToI444, BGRAToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifBGRAToI422, BGRAToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifBGRAToJ422, BGRAToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifBGRAToJ420, BGRAToARGB, ARGBToJ420, AVIF_PIXEL_FORMAT_YUV420)
// AVIF_RGB_FORMAT_BGR
AVIF_DEFINE_CONVERSION(avifRGB24ToI444, RGB24ToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifRGB24ToI422, RGB24ToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifRGB24ToJ422, RGB24ToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
// AVIF_RGB_FORMAT_ABGR
AVIF_DEFINE_CONVERSION(avifRGBAToI444, RGBAToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifRGBAToI422, RGBAToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifRGBAToJ422, RGBAToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifRGBAToJ420, RGBAToARGB, ARGBToJ420, AVIF_PIXEL_FORMAT_YUV420)

//--------------------------------------------------------------------------------------------------
// RGB to YUV

static avifResult avifImageRGBToYUVLibYUV8bpc(avifImage * image, const avifRGBImage * rgb);

avifResult avifImageRGBToYUVLibYUV(avifImage * image, const avifRGBImage * rgb)
{
    // The width, height, and stride parameters of libyuv functions are all of the int type.
    if (image->width > INT_MAX || image->height > INT_MAX || image->yuvRowBytes[AVIF_CHAN_Y] > INT_MAX || rgb->rowBytes > INT_MAX) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }

    if ((image->depth == 8) && (rgb->depth == 8)) {
        return avifImageRGBToYUVLibYUV8bpc(image, rgb);
    }

    // This function didn't do anything; use the built-in conversion.
    return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult avifImageRGBToYUVLibYUV8bpc(avifImage * image, const avifRGBImage * rgb)
{
    assert((image->depth == 8) && (rgb->depth == 8));
    // libavif uses byte-order when describing pixel formats, such that the R in RGBA is the lowest address,
    // similar to PNG. libyuv orders in word-order, so libavif's RGBA would be referred to in libyuv as ABGR.

    // libyuv only handles BT.601 for RGB to YUV, and not all range/order/subsampling combinations.
    // BT.470BG has the same coefficients as BT.601.
    if ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT470BG) || (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT601)) {
        if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
            // Lookup table for RGB To Y (monochrome).
            typedef int (*RGBtoY)(const uint8_t *, int, uint8_t *, int, int, int);
            // First dimension is for avifRange.
            RGBtoY lutRgbToY[2][AVIF_RGB_FORMAT_COUNT] = { // AVIF_RANGE_LIMITED
                                                           {
                                                               //          // AVIF_RGB_FORMAT_
                                                               NULL,       // RGB
                                                               NULL,       // RGBA
                                                               NULL,       // ARGB
                                                               NULL,       // BGR
                                                               ARGBToI400, // BGRA
                                                               NULL,       // ABGR
                                                               NULL,       // RGB_565
                                                           },
                                                           // AVIF_RANGE_FULL
                                                           {
                                                               //           // AVIF_RGB_FORMAT_
                                                               RAWToJ400,   // RGB
                                                               ABGRToJ400,  // RGBA
                                                               NULL,        // ARGB
                                                               RGB24ToJ400, // BGR
                                                               ARGBToJ400,  // BGRA
                                                               RGBAToJ400,  // ABGR
                                                               NULL         // RGB_565
                                                           }
            };
            RGBtoY rgbToY = lutRgbToY[image->yuvRange][rgb->format];
            if (rgbToY != NULL) {
                if (rgbToY(rgb->pixels,
                           rgb->rowBytes,
                           image->yuvPlanes[AVIF_CHAN_Y],
                           image->yuvRowBytes[AVIF_CHAN_Y],
                           image->width,
                           image->height) != 0) {
                    return AVIF_RESULT_REFORMAT_FAILED;
                }
                return AVIF_RESULT_OK;
            }
        } else {
            // Lookup table for RGB To YUV Matrix (average filter).
            typedef int (*RGBtoYUV)(const uint8_t *, int, uint8_t *, int, uint8_t *, int, uint8_t *, int, int, int);
            // First dimension is for avifRange.
            RGBtoYUV lutRgbToYuv[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
                // AVIF_RANGE_LIMITED
                {
                    // { NONE,    YUV444,    YUV422,    YUV420,    YUV400 }        // AVIF_RGB_FORMAT_
                    { NULL, RAWToI444, avifRAWToI422, RAWToI420, NULL },           // RGB
                    { NULL, avifABGRToI444, avifABGRToI422, ABGRToI420, NULL },    // RGBA
                    { NULL, avifBGRAToI444, avifBGRAToI422, BGRAToI420, NULL },    // ARGB
                    { NULL, avifRGB24ToI444, avifRGB24ToI422, RGB24ToI420, NULL }, // BGR
                    { NULL, ARGBToI444, ARGBToI422, ARGBToI420, NULL },            // BGRA
                    { NULL, avifRGBAToI444, avifRGBAToI422, RGBAToI420, NULL },    // ABGR
                    { NULL, NULL, NULL, NULL, NULL }                               // RGB_565
                },
                // AVIF_RANGE_FULL
                {
                    // { NONE, YUV444, YUV422,   YUV420,   YUV400 }       // AVIF_RGB_FORMAT_
                    { NULL, RAWToJ444, avifRAWToJ422, RAWToJ420, NULL },  // RGB
                    { NULL, NULL, ABGRToJ422, ABGRToJ420, NULL },         // RGBA
                    { NULL, NULL, avifBGRAToJ422, avifBGRAToJ420, NULL }, // ARGB
                    { NULL, NULL, avifRGB24ToJ422, RGB24ToJ420, NULL },   // BGR
                    { NULL, NULL, ARGBToJ422, ARGBToJ420, NULL },         // BGRA
                    { NULL, NULL, avifRGBAToJ422, avifRGBAToJ420, NULL }, // ABGR
                    { NULL, NULL, NULL, NULL, NULL }                      // RGB_565
                }
            };
            RGBtoYUV rgbToYuv = lutRgbToYuv[image->yuvRange][rgb->format][image->yuvFormat];
            if (rgbToYuv != NULL) {
                if (rgbToYuv(rgb->pixels,
                             rgb->rowBytes,
                             image->yuvPlanes[AVIF_CHAN_Y],
                             image->yuvRowBytes[AVIF_CHAN_Y],
                             image->yuvPlanes[AVIF_CHAN_U],
                             image->yuvRowBytes[AVIF_CHAN_U],
                             image->yuvPlanes[AVIF_CHAN_V],
                             image->yuvRowBytes[AVIF_CHAN_V],
                             image->width,
                             image->height) != 0) {
                    return AVIF_RESULT_REFORMAT_FAILED;
                }
                return AVIF_RESULT_OK;
            }
        }
    }
    // TODO: Use SplitRGBPlane() for AVIF_MATRIX_COEFFICIENTS_IDENTITY if faster than the built-in implementation
    return AVIF_RESULT_NOT_IMPLEMENTED;
}

//--------------------------------------------------------------------------------------------------
// YUV to RGB

// Note about the libyuv look up tables used for YUV-to-RGB conversion:
// libavif uses byte-order when describing pixel formats, such that the R in RGBA is the lowest address, similar to PNG. libyuv
// orders in word-order, so libavif's RGBA would be referred to in libyuv as ABGR.  In addition, swapping U and V in any of the
// calls, along with using the Yvu matrix instead of Yuv matrix, swaps B and R in these orderings as well.
//
// libavif format            libyuv Func      UV matrix (and UV argument ordering)
// --------------------      -------------    ------------------------------------
// For 8-bit YUV:
// AVIF_RGB_FORMAT_RGB       *ToRGB24Matrix   matrixYVU
// AVIF_RGB_FORMAT_RGBA      *ToARGBMatrix    matrixYVU
// AVIF_RGB_FORMAT_ARGB      *ToRGBAMatrix    matrixYVU
// AVIF_RGB_FORMAT_BGR       *ToRGB24Matrix   matrixYUV
// AVIF_RGB_FORMAT_BGRA      *ToARGBMatrix    matrixYUV
// AVIF_RGB_FORMAT_ABGR      *ToRGBAMatrix    matrixYUV
// AVIF_RGB_FORMAT_RGB_565   *ToRGB565Matrix  matrixYUV
//
// For 10-bit and 12-bit YUV:
// AVIF_RGB_FORMAT_RGB       n/a              n/a
// AVIF_RGB_FORMAT_RGBA      *ToARGBMatrix    matrixYVU
// AVIF_RGB_FORMAT_ARGB      n/a              n/a
// AVIF_RGB_FORMAT_BGR       n/a              n/a
// AVIF_RGB_FORMAT_BGRA      *ToARGBMatrix    matrixYUV
// AVIF_RGB_FORMAT_ABGR      n/a              n/a
// AVIF_RGB_FORMAT_RGB_565   n/a              n/a

// Lookup table for isYVU. If the entry in this table is AVIF_TRUE, then it
// means that we are using a libyuv function with R and B channels swapped,
// which requires U and V planes also be swapped.
static const avifBool lutIsYVU[AVIF_RGB_FORMAT_COUNT] = {
    //          // AVIF_RGB_FORMAT_
    AVIF_TRUE,  // RGB
    AVIF_TRUE,  // RGBA
    AVIF_TRUE,  // ARGB
    AVIF_FALSE, // BGR
    AVIF_FALSE, // BGRA
    AVIF_FALSE, // ABGR
    AVIF_FALSE, // RGB_565
};

typedef int (*YUV400ToRGBMatrix)(const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
typedef int (*YUVToRGBMatrixFilter)(const uint8_t *,
                                    int,
                                    const uint8_t *,
                                    int,
                                    const uint8_t *,
                                    int,
                                    uint8_t *,
                                    int,
                                    const struct YuvConstants *,
                                    int,
                                    int,
                                    enum FilterMode);
typedef int (*YUVAToRGBMatrixFilter)(const uint8_t *,
                                     int,
                                     const uint8_t *,
                                     int,
                                     const uint8_t *,
                                     int,
                                     const uint8_t *,
                                     int,
                                     uint8_t *,
                                     int,
                                     const struct YuvConstants *,
                                     int,
                                     int,
                                     int,
                                     enum FilterMode);
typedef int (*YUVToRGBMatrix)(const uint8_t *, int, const uint8_t *, int, const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
typedef int (*YUVAToRGBMatrix)(const uint8_t *,
                               int,
                               const uint8_t *,
                               int,
                               const uint8_t *,
                               int,
                               const uint8_t *,
                               int,
                               uint8_t *,
                               int,
                               const struct YuvConstants *,
                               int,
                               int,
                               int);
typedef int (*YUVToRGBMatrixFilterHighBitDepth)(const uint16_t *,
                                                int,
                                                const uint16_t *,
                                                int,
                                                const uint16_t *,
                                                int,
                                                uint8_t *,
                                                int,
                                                const struct YuvConstants *,
                                                int,
                                                int,
                                                enum FilterMode);
typedef int (*YUVAToRGBMatrixFilterHighBitDepth)(const uint16_t *,
                                                 int,
                                                 const uint16_t *,
                                                 int,
                                                 const uint16_t *,
                                                 int,
                                                 const uint16_t *,
                                                 int,
                                                 uint8_t *,
                                                 int,
                                                 const struct YuvConstants *,
                                                 int,
                                                 int,
                                                 int,
                                                 enum FilterMode);
typedef int (*YUVToRGBMatrixHighBitDepth)(const uint16_t *,
                                          int,
                                          const uint16_t *,
                                          int,
                                          const uint16_t *,
                                          int,
                                          uint8_t *,
                                          int,
                                          const struct YuvConstants *,
                                          int,
                                          int);
typedef int (*YUVAToRGBMatrixHighBitDepth)(const uint16_t *,
                                           int,
                                           const uint16_t *,
                                           int,
                                           const uint16_t *,
                                           int,
                                           const uint16_t *,
                                           int,
                                           uint8_t *,
                                           int,
                                           const struct YuvConstants *,
                                           int,
                                           int,
                                           int);

// At most one pointer in this struct will be not-NULL.
typedef struct
{
    YUV400ToRGBMatrix yuv400ToRgbMatrix;
    YUVToRGBMatrixFilter yuvToRgbMatrixFilter;
    YUVAToRGBMatrixFilter yuvaToRgbMatrixFilter;
    YUVToRGBMatrix yuvToRgbMatrix;
    YUVAToRGBMatrix yuvaToRgbMatrix;
    YUVToRGBMatrixFilterHighBitDepth yuvToRgbMatrixFilterHighBitDepth;
    YUVAToRGBMatrixFilterHighBitDepth yuvaToRgbMatrixFilterHighBitDepth;
    YUVToRGBMatrixHighBitDepth yuvToRgbMatrixHighBitDepth;
    YUVAToRGBMatrixHighBitDepth yuvaToRgbMatrixHighBitDepth;
} LibyuvConversionFunction;

// Only allow nearest-neighbor filter if explicitly specified or left as default.
static avifBool nearestNeighborFilterAllowed(int chromaUpsampling)
{
    return chromaUpsampling != AVIF_CHROMA_UPSAMPLING_BILINEAR && chromaUpsampling != AVIF_CHROMA_UPSAMPLING_BEST_QUALITY;
}

// Returns AVIF_TRUE if the given yuvFormat and yuvDepth can be converted to 8-bit RGB using libyuv, AVIF_FALSE otherwise. When
// AVIF_TRUE is returned, exactly one function pointers will be populated with the appropriate conversion function. If
// alphaPreferred is set to AVIF_TRUE, then a function that can also copy the alpha channel will be preferred if available.
static avifBool getLibYUVConversionFunction(avifPixelFormat yuvFormat,
                                            int yuvDepth,
                                            avifRGBImage * rgb,
                                            avifBool alphaPreferred,
                                            LibyuvConversionFunction * lcf)
{
    // Lookup table for 8-bit YUV400 to 8-bit RGB Matrix.
    static const YUV400ToRGBMatrix lutYuv400ToRgbMatrix[AVIF_RGB_FORMAT_COUNT] = {
        //                // AVIF_RGB_FORMAT_
        NULL,             // RGB
        I400ToARGBMatrix, // RGBA
        NULL,             // ARGB
        NULL,             // BGR
        I400ToARGBMatrix, // BGRA
        NULL,             // ABGR
        NULL,             // RGB_565
    };

    // Lookup table for 8-bit YUV To 8-bit RGB Matrix (with filter).
    static const YUVToRGBMatrixFilter lutYuvToRgbMatrixFilter[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
        { NULL, NULL, I422ToRGB24MatrixFilter, I420ToRGB24MatrixFilter, NULL }, // RGB
        { NULL, NULL, I422ToARGBMatrixFilter, I420ToARGBMatrixFilter, NULL },   // RGBA
        { NULL, NULL, NULL, NULL, NULL },                                       // ARGB
        { NULL, NULL, I422ToRGB24MatrixFilter, I420ToRGB24MatrixFilter, NULL }, // BGR
        { NULL, NULL, I422ToARGBMatrixFilter, I420ToARGBMatrixFilter, NULL },   // BGRA
        { NULL, NULL, NULL, NULL, NULL },                                       // ABGR
        { NULL, NULL, NULL, NULL, NULL },                                       // RGB_565
    };

    // Lookup table for 8-bit YUVA To 8-bit RGB Matrix (with filter).
    static const YUVAToRGBMatrixFilter lutYuvaToRgbMatrixFilter[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
        { NULL, NULL, NULL, NULL, NULL },                                               // RGB
        { NULL, NULL, I422AlphaToARGBMatrixFilter, I420AlphaToARGBMatrixFilter, NULL }, // RGBA
        { NULL, NULL, NULL, NULL, NULL },                                               // ARGB
        { NULL, NULL, NULL, NULL, NULL },                                               // BGR
        { NULL, NULL, I422AlphaToARGBMatrixFilter, I420AlphaToARGBMatrixFilter, NULL }, // BGRA
        { NULL, NULL, NULL, NULL, NULL },                                               // ABGR
        { NULL, NULL, NULL, NULL, NULL },                                               // RGB_565
    };

    // Lookup table for 8-bit YUV To 8-bit RGB Matrix (4:4:4 or nearest-neighbor filter).
    static const YUVToRGBMatrix lutYuvToRgbMatrix[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
        { NULL, I444ToRGB24Matrix, NULL, I420ToRGB24Matrix, NULL },           // RGB
        { NULL, I444ToARGBMatrix, I422ToARGBMatrix, I420ToARGBMatrix, NULL }, // RGBA
        { NULL, NULL, I422ToRGBAMatrix, I420ToRGBAMatrix, NULL },             // ARGB
        { NULL, I444ToRGB24Matrix, NULL, I420ToRGB24Matrix, NULL },           // BGR
        { NULL, I444ToARGBMatrix, I422ToARGBMatrix, I420ToARGBMatrix, NULL }, // BGRA
        { NULL, NULL, I422ToRGBAMatrix, I420ToRGBAMatrix, NULL },             // ABGR
        { NULL, NULL, I422ToRGB565Matrix, I420ToRGB565Matrix, NULL },         // RGB_565
    };

    // Lookup table for 8-bit YUVA To 8-bit RGB Matrix (4:4:4 or nearest-neighbor filter).
    static const YUVAToRGBMatrix lutYuvaToRgbMatrix[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
        { NULL, NULL, NULL, NULL, NULL },                                                    // RGB
        { NULL, I444AlphaToARGBMatrix, I422AlphaToARGBMatrix, I420AlphaToARGBMatrix, NULL }, // RGBA
        { NULL, NULL, NULL, NULL, NULL },                                                    // ARGB
        { NULL, NULL, NULL, NULL, NULL },                                                    // BGR
        { NULL, I444AlphaToARGBMatrix, I422AlphaToARGBMatrix, I420AlphaToARGBMatrix, NULL }, // BGRA
        { NULL, NULL, NULL, NULL, NULL },                                                    // ABGR
        { NULL, NULL, NULL, NULL, NULL },                                                    // RGB_565
    };

    // Lookup table for YUV To RGB Matrix (with filter).  First dimension is for the YUV bit depth.
    static const YUVToRGBMatrixFilterHighBitDepth lutYuvToRgbMatrixFilterHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // 10bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL },                                     // RGB
            { NULL, NULL, I210ToARGBMatrixFilter, I010ToARGBMatrixFilter, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL },                                     // ARGB
            { NULL, NULL, NULL, NULL, NULL },                                     // BGR
            { NULL, NULL, I210ToARGBMatrixFilter, I010ToARGBMatrixFilter, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL },                                     // ABGR
            { NULL, NULL, NULL, NULL, NULL },                                     // RGB_565
        },
        // 12bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 } // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL }, // RGB
            { NULL, NULL, NULL, NULL, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL }, // ARGB
            { NULL, NULL, NULL, NULL, NULL }, // BGR
            { NULL, NULL, NULL, NULL, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL }, // ABGR
            { NULL, NULL, NULL, NULL, NULL }, // RGB_565
        },
    };

    // Lookup table for YUVA To RGB Matrix (with filter).  First dimension is for the YUV bit depth.
    static const YUVAToRGBMatrixFilterHighBitDepth lutYuvaToRgbMatrixFilterHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // 10bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL },                                               // RGB
            { NULL, NULL, I210AlphaToARGBMatrixFilter, I010AlphaToARGBMatrixFilter, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL },                                               // ARGB
            { NULL, NULL, NULL, NULL, NULL },                                               // BGR
            { NULL, NULL, I210AlphaToARGBMatrixFilter, I010AlphaToARGBMatrixFilter, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL },                                               // ABGR
            { NULL, NULL, NULL, NULL, NULL },                                               // RGB_565
        },
        // 12bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 } // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL }, // RGB
            { NULL, NULL, NULL, NULL, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL }, // ARGB
            { NULL, NULL, NULL, NULL, NULL }, // BGR
            { NULL, NULL, NULL, NULL, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL }, // ABGR
            { NULL, NULL, NULL, NULL, NULL }, // RGB_565
        },
    };

    // Lookup table for YUV To RGB Matrix (4:4:4 or nearest-neighbor filter).  First dimension is for the YUV bit depth.
    static const YUVToRGBMatrixHighBitDepth lutYuvToRgbMatrixHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // 10bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL },                                     // RGB
            { NULL, I410ToARGBMatrix, I210ToARGBMatrix, I010ToARGBMatrix, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL },                                     // ARGB
            { NULL, NULL, NULL, NULL, NULL },                                     // BGR
            { NULL, I410ToARGBMatrix, I210ToARGBMatrix, I010ToARGBMatrix, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL },                                     // ABGR
            { NULL, NULL, NULL, NULL, NULL },                                     // RGB_565
        },
        // 12bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }   // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL },             // RGB
            { NULL, NULL, NULL, I012ToARGBMatrix, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL },             // ARGB
            { NULL, NULL, NULL, NULL, NULL },             // BGR
            { NULL, NULL, NULL, I012ToARGBMatrix, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL },             // ABGR
            { NULL, NULL, NULL, NULL, NULL },             // RGB_565
        },
    };

    // Lookup table for YUVA To RGB Matrix (4:4:4 or nearest-neighbor filter).  First dimension is for the YUV bit depth.
    static const YUVAToRGBMatrixHighBitDepth lutYuvaToRgbMatrixHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
        // 10bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }                           // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL },                                                    // RGB
            { NULL, I410AlphaToARGBMatrix, I210AlphaToARGBMatrix, I010AlphaToARGBMatrix, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL },                                                    // ARGB
            { NULL, NULL, NULL, NULL, NULL },                                                    // BGR
            { NULL, I410AlphaToARGBMatrix, I210AlphaToARGBMatrix, I010AlphaToARGBMatrix, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL },                                                    // ABGR
            { NULL, NULL, NULL, NULL, NULL },                                                    // RGB_565
        },
        // 12bpc
        {
            // { NONE, YUV444, YUV422, YUV420, YUV400 }   // AVIF_RGB_FORMAT_
            { NULL, NULL, NULL, NULL, NULL }, // RGB
            { NULL, NULL, NULL, NULL, NULL }, // RGBA
            { NULL, NULL, NULL, NULL, NULL }, // ARGB
            { NULL, NULL, NULL, NULL, NULL }, // BGR
            { NULL, NULL, NULL, NULL, NULL }, // BGRA
            { NULL, NULL, NULL, NULL, NULL }, // ABGR
            { NULL, NULL, NULL, NULL, NULL }, // RGB_565
        },
    };

    memset(lcf, 0, sizeof(*lcf));
    assert(rgb->depth == 8);
    if (yuvDepth > 8) {
        assert(yuvDepth == 10 || yuvDepth == 12);
        int depthIndex = (yuvDepth == 10) ? 0 : 1;
        if (yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
            if (alphaPreferred) {
                lcf->yuvaToRgbMatrixFilterHighBitDepth = lutYuvaToRgbMatrixFilterHighBitDepth[depthIndex][rgb->format][yuvFormat];
                if (lcf->yuvaToRgbMatrixFilterHighBitDepth != NULL) {
                    return AVIF_TRUE;
                }
            }
            lcf->yuvToRgbMatrixFilterHighBitDepth = lutYuvToRgbMatrixFilterHighBitDepth[depthIndex][rgb->format][yuvFormat];
            if (lcf->yuvToRgbMatrixFilterHighBitDepth != NULL) {
                return AVIF_TRUE;
            }
        }
        if (yuvFormat == AVIF_PIXEL_FORMAT_YUV444 || nearestNeighborFilterAllowed(rgb->chromaUpsampling)) {
            if (alphaPreferred) {
                lcf->yuvaToRgbMatrixHighBitDepth = lutYuvaToRgbMatrixHighBitDepth[depthIndex][rgb->format][yuvFormat];
                if (lcf->yuvaToRgbMatrixHighBitDepth != NULL) {
                    return AVIF_TRUE;
                }
            }
            lcf->yuvToRgbMatrixHighBitDepth = lutYuvToRgbMatrixHighBitDepth[depthIndex][rgb->format][yuvFormat];
            if (lcf->yuvToRgbMatrixHighBitDepth != NULL) {
                return AVIF_TRUE;
            }
        }
        // Fallthrough is intentional. No high bitdepth libyuv function was found. Check if there is an 8-bit libyuv function which
        // can used with a downshift.
    }
    if (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
        lcf->yuv400ToRgbMatrix = lutYuv400ToRgbMatrix[rgb->format];
        return lcf->yuv400ToRgbMatrix != NULL;
    }
    if (yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
        if (alphaPreferred) {
            lcf->yuvaToRgbMatrixFilter = lutYuvaToRgbMatrixFilter[rgb->format][yuvFormat];
            if (lcf->yuvaToRgbMatrixFilter != NULL) {
                return AVIF_TRUE;
            }
        }
        lcf->yuvToRgbMatrixFilter = lutYuvToRgbMatrixFilter[rgb->format][yuvFormat];
        if (lcf->yuvToRgbMatrixFilter != NULL) {
            return AVIF_TRUE;
        }
        if (!nearestNeighborFilterAllowed(rgb->chromaUpsampling)) {
            return AVIF_FALSE;
        }
    }
    if (alphaPreferred) {
        lcf->yuvaToRgbMatrix = lutYuvaToRgbMatrix[rgb->format][yuvFormat];
        if (lcf->yuvaToRgbMatrix != NULL) {
            return AVIF_TRUE;
        }
    }
    lcf->yuvToRgbMatrix = lutYuvToRgbMatrix[rgb->format][yuvFormat];
    return lcf->yuvToRgbMatrix != NULL;
}

static void getLibYUVConstants(const avifImage * image, const struct YuvConstants ** matrixYUV, const struct YuvConstants ** matrixYVU)
{
    // Allow the identity matrix to be used with YUV 4:0:0. Replace the identity matrix with
    // MatrixCoefficients 6 (BT.601).
    const avifBool yuv400WithIdentityMatrix = (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) &&
                                              (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY);
    const avifMatrixCoefficients matrixCoefficients = yuv400WithIdentityMatrix ? AVIF_MATRIX_COEFFICIENTS_BT601 : image->matrixCoefficients;
    if (image->yuvRange == AVIF_RANGE_FULL) {
        switch (matrixCoefficients) {
            // BT.709 full range YuvConstants were added in libyuv version 1772.
            // See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/2646472.
            case AVIF_MATRIX_COEFFICIENTS_BT709:
#if LIBYUV_VERSION >= 1772
                *matrixYUV = &kYuvF709Constants;
                *matrixYVU = &kYvuF709Constants;
#endif
                break;
            case AVIF_MATRIX_COEFFICIENTS_BT470BG:
            case AVIF_MATRIX_COEFFICIENTS_BT601:
            case AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED:
                *matrixYUV = &kYuvJPEGConstants;
                *matrixYVU = &kYvuJPEGConstants;
                break;
            // BT.2020 full range YuvConstants were added in libyuv version 1775.
            // See https://chromium-review.googlesource.com/c/libyuv/libyuv/+/2678859.
            case AVIF_MATRIX_COEFFICIENTS_BT2020_NCL:
#if LIBYUV_VERSION >= 1775
                *matrixYUV = &kYuvV2020Constants;
                *matrixYVU = &kYvuV2020Constants;
#endif
                break;
            case AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL:
                switch (image->colorPrimaries) {
                    case AVIF_COLOR_PRIMARIES_BT709:
                    case AVIF_COLOR_PRIMARIES_UNSPECIFIED:
#if LIBYUV_VERSION >= 1772
                        *matrixYUV = &kYuvF709Constants;
                        *matrixYVU = &kYvuF709Constants;
#endif
                        break;
                    case AVIF_COLOR_PRIMARIES_BT470BG:
                    case AVIF_COLOR_PRIMARIES_BT601:
                        *matrixYUV = &kYuvJPEGConstants;
                        *matrixYVU = &kYvuJPEGConstants;
                        break;
                    case AVIF_COLOR_PRIMARIES_BT2020:
#if LIBYUV_VERSION >= 1775
                        *matrixYUV = &kYuvV2020Constants;
                        *matrixYVU = &kYvuV2020Constants;
#endif
                        break;

                    case AVIF_COLOR_PRIMARIES_UNKNOWN:
                    case AVIF_COLOR_PRIMARIES_BT470M:
                    case AVIF_COLOR_PRIMARIES_SMPTE240:
                    case AVIF_COLOR_PRIMARIES_GENERIC_FILM:
                    case AVIF_COLOR_PRIMARIES_XYZ:
                    case AVIF_COLOR_PRIMARIES_SMPTE431:
                    case AVIF_COLOR_PRIMARIES_SMPTE432:
                    case AVIF_COLOR_PRIMARIES_EBU3213:
                        break;
                }
                break;

            case AVIF_MATRIX_COEFFICIENTS_IDENTITY:
            case AVIF_MATRIX_COEFFICIENTS_FCC:
            case AVIF_MATRIX_COEFFICIENTS_SMPTE240:
            case AVIF_MATRIX_COEFFICIENTS_YCGCO:
            case AVIF_MATRIX_COEFFICIENTS_BT2020_CL:
            case AVIF_MATRIX_COEFFICIENTS_SMPTE2085:
            case AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL:
            case AVIF_MATRIX_COEFFICIENTS_ICTCP:
                break;
        }
    } else { // image->yuvRange == AVIF_RANGE_LIMITED
        switch (matrixCoefficients) {
            case AVIF_MATRIX_COEFFICIENTS_BT709:
                *matrixYUV = &kYuvH709Constants;
                *matrixYVU = &kYvuH709Constants;
                break;
            case AVIF_MATRIX_COEFFICIENTS_BT470BG:
            case AVIF_MATRIX_COEFFICIENTS_BT601:
            case AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED:
                *matrixYUV = &kYuvI601Constants;
                *matrixYVU = &kYvuI601Constants;
                break;
            case AVIF_MATRIX_COEFFICIENTS_BT2020_NCL:
                *matrixYUV = &kYuv2020Constants;
                *matrixYVU = &kYvu2020Constants;
                break;
            case AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL:
                switch (image->colorPrimaries) {
                    case AVIF_COLOR_PRIMARIES_BT709:
                    case AVIF_COLOR_PRIMARIES_UNSPECIFIED:
                        *matrixYUV = &kYuvH709Constants;
                        *matrixYVU = &kYvuH709Constants;
                        break;
                    case AVIF_COLOR_PRIMARIES_BT470BG:
                    case AVIF_COLOR_PRIMARIES_BT601:
                        *matrixYUV = &kYuvI601Constants;
                        *matrixYVU = &kYvuI601Constants;
                        break;
                    case AVIF_COLOR_PRIMARIES_BT2020:
                        *matrixYUV = &kYuv2020Constants;
                        *matrixYVU = &kYvu2020Constants;
                        break;

                    case AVIF_COLOR_PRIMARIES_UNKNOWN:
                    case AVIF_COLOR_PRIMARIES_BT470M:
                    case AVIF_COLOR_PRIMARIES_SMPTE240:
                    case AVIF_COLOR_PRIMARIES_GENERIC_FILM:
                    case AVIF_COLOR_PRIMARIES_XYZ:
                    case AVIF_COLOR_PRIMARIES_SMPTE431:
                    case AVIF_COLOR_PRIMARIES_SMPTE432:
                    case AVIF_COLOR_PRIMARIES_EBU3213:
                        break;
                }
                break;
            case AVIF_MATRIX_COEFFICIENTS_IDENTITY:
            case AVIF_MATRIX_COEFFICIENTS_FCC:
            case AVIF_MATRIX_COEFFICIENTS_SMPTE240:
            case AVIF_MATRIX_COEFFICIENTS_YCGCO:
            case AVIF_MATRIX_COEFFICIENTS_BT2020_CL:
            case AVIF_MATRIX_COEFFICIENTS_SMPTE2085:
            case AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL:
            case AVIF_MATRIX_COEFFICIENTS_ICTCP:
                break;
        }
    }
}

static avifResult avifImageDownshiftTo8bpc(const avifImage * image, avifImage * image8, avifBool downshiftAlpha)
{
    avifImageSetDefaults(image8);
    avifImageCopyNoAlloc(image8, image);
    image8->depth = 8;
    // downshiftAlpha will be true only if the image has an alpha plane. So it is safe to pass AVIF_PLANES_ALL here in that case.
    assert(!downshiftAlpha || image->alphaPlane);
    AVIF_CHECKRES(avifImageAllocatePlanes(image8, downshiftAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV));
    // 16384 for 10-bit and 4096 for 12-bit.
    const int scale = 1 << (24 - image->depth);
    for (int plane = AVIF_CHAN_Y; plane <= (downshiftAlpha ? AVIF_CHAN_A : AVIF_CHAN_V); ++plane) {
        const uint32_t planeWidth = avifImagePlaneWidth(image, plane);
        if (planeWidth == 0) {
            continue;
        }
        Convert16To8Plane((const uint16_t *)avifImagePlane(image, plane),
                          avifImagePlaneRowBytes(image, plane) / 2,
                          avifImagePlane(image8, plane),
                          avifImagePlaneRowBytes(image8, plane),
                          scale,
                          planeWidth,
                          avifImagePlaneHeight(image, plane));
    }
    return AVIF_RESULT_OK;
}

IGNORE_CFI_ICALL avifResult avifImageYUVToRGBLibYUV(const avifImage * image, avifRGBImage * rgb, avifBool reformatAlpha, avifBool * alphaReformattedWithLibYUV)
{
    *alphaReformattedWithLibYUV = AVIF_FALSE;
    // The width, height, and stride parameters of libyuv functions are all of the int type.
    if (image->width > INT_MAX || image->height > INT_MAX || image->yuvRowBytes[AVIF_CHAN_Y] > INT_MAX || rgb->rowBytes > INT_MAX) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    if (rgb->depth != 8 || (image->depth != 8 && image->depth != 10 && image->depth != 12)) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    // Find the correct libyuv YuvConstants, based on range and CP/MC
    const struct YuvConstants * matrixYUV = NULL;
    const struct YuvConstants * matrixYVU = NULL;
    getLibYUVConstants(image, &matrixYUV, &matrixYVU);
    if (!matrixYVU) {
        // No YuvConstants exist for the current image; use the built-in YUV conversion
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }

    LibyuvConversionFunction lcf;
    const avifBool alphaPreferred = reformatAlpha && image->alphaPlane && image->alphaRowBytes;
    if (!getLibYUVConversionFunction(image->yuvFormat, image->depth, rgb, alphaPreferred, &lcf)) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    if (!image->alphaPlane || !image->alphaRowBytes) {
        // If the image does not have an alpha plane, then libyuv always prefills the output RGB image with opaque alpha values.
        *alphaReformattedWithLibYUV = AVIF_TRUE;
    }
    avifBool isYVU = lutIsYVU[rgb->format];
    const struct YuvConstants * matrix = isYVU ? matrixYVU : matrixYUV;
    int libyuvResult = -1;
    int uPlaneIndex = isYVU ? AVIF_CHAN_V : AVIF_CHAN_U;
    int vPlaneIndex = isYVU ? AVIF_CHAN_U : AVIF_CHAN_V;
    const enum FilterMode filter =
        ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST))
            ? kFilterNone
            : kFilterBilinear;
    if (lcf.yuvToRgbMatrixFilterHighBitDepth != NULL) {
        libyuvResult = lcf.yuvToRgbMatrixFilterHighBitDepth((const uint16_t *)image->yuvPlanes[AVIF_CHAN_Y],
                                                            image->yuvRowBytes[AVIF_CHAN_Y] / 2,
                                                            (const uint16_t *)image->yuvPlanes[uPlaneIndex],
                                                            image->yuvRowBytes[uPlaneIndex] / 2,
                                                            (const uint16_t *)image->yuvPlanes[vPlaneIndex],
                                                            image->yuvRowBytes[vPlaneIndex] / 2,
                                                            rgb->pixels,
                                                            rgb->rowBytes,
                                                            matrix,
                                                            image->width,
                                                            image->height,
                                                            filter);
    } else if (lcf.yuvaToRgbMatrixFilterHighBitDepth != NULL) {
        libyuvResult = lcf.yuvaToRgbMatrixFilterHighBitDepth((const uint16_t *)image->yuvPlanes[AVIF_CHAN_Y],
                                                             image->yuvRowBytes[AVIF_CHAN_Y] / 2,
                                                             (const uint16_t *)image->yuvPlanes[uPlaneIndex],
                                                             image->yuvRowBytes[uPlaneIndex] / 2,
                                                             (const uint16_t *)image->yuvPlanes[vPlaneIndex],
                                                             image->yuvRowBytes[vPlaneIndex] / 2,
                                                             (const uint16_t *)image->alphaPlane,
                                                             image->alphaRowBytes / 2,
                                                             rgb->pixels,
                                                             rgb->rowBytes,
                                                             matrix,
                                                             image->width,
                                                             image->height,
                                                             /*attenuate=*/0,
                                                             filter);
        *alphaReformattedWithLibYUV = AVIF_TRUE;
    } else if (lcf.yuvToRgbMatrixHighBitDepth != NULL) {
        libyuvResult = lcf.yuvToRgbMatrixHighBitDepth((const uint16_t *)image->yuvPlanes[AVIF_CHAN_Y],
                                                      image->yuvRowBytes[AVIF_CHAN_Y] / 2,
                                                      (const uint16_t *)image->yuvPlanes[uPlaneIndex],
                                                      image->yuvRowBytes[uPlaneIndex] / 2,
                                                      (const uint16_t *)image->yuvPlanes[vPlaneIndex],
                                                      image->yuvRowBytes[vPlaneIndex] / 2,
                                                      rgb->pixels,
                                                      rgb->rowBytes,
                                                      matrix,
                                                      image->width,
                                                      image->height);
    } else if (lcf.yuvaToRgbMatrixHighBitDepth != NULL) {
        libyuvResult = lcf.yuvaToRgbMatrixHighBitDepth((const uint16_t *)image->yuvPlanes[AVIF_CHAN_Y],
                                                       image->yuvRowBytes[AVIF_CHAN_Y] / 2,
                                                       (const uint16_t *)image->yuvPlanes[uPlaneIndex],
                                                       image->yuvRowBytes[uPlaneIndex] / 2,
                                                       (const uint16_t *)image->yuvPlanes[vPlaneIndex],
                                                       image->yuvRowBytes[vPlaneIndex] / 2,
                                                       (const uint16_t *)image->alphaPlane,
                                                       image->alphaRowBytes / 2,
                                                       rgb->pixels,
                                                       rgb->rowBytes,
                                                       matrix,
                                                       image->width,
                                                       image->height,
                                                       /*attentuate=*/0);
        *alphaReformattedWithLibYUV = AVIF_TRUE;
    } else {
        avifImage image8;
        avifBool inputIsHighBitDepth = image->depth > 8;
        if (inputIsHighBitDepth) {
            const avifBool downshiftAlpha = (lcf.yuvaToRgbMatrixFilter != NULL || lcf.yuvaToRgbMatrix != NULL);
            AVIF_CHECKRES(avifImageDownshiftTo8bpc(image, &image8, downshiftAlpha));
            image = &image8;
        }
        if (lcf.yuv400ToRgbMatrix != NULL) {
            libyuvResult = lcf.yuv400ToRgbMatrix(image->yuvPlanes[AVIF_CHAN_Y],
                                                 image->yuvRowBytes[AVIF_CHAN_Y],
                                                 rgb->pixels,
                                                 rgb->rowBytes,
                                                 matrix,
                                                 image->width,
                                                 image->height);
        } else if (lcf.yuvToRgbMatrixFilter != NULL) {
            libyuvResult = lcf.yuvToRgbMatrixFilter(image->yuvPlanes[AVIF_CHAN_Y],
                                                    image->yuvRowBytes[AVIF_CHAN_Y],
                                                    image->yuvPlanes[uPlaneIndex],
                                                    image->yuvRowBytes[uPlaneIndex],
                                                    image->yuvPlanes[vPlaneIndex],
                                                    image->yuvRowBytes[vPlaneIndex],
                                                    rgb->pixels,
                                                    rgb->rowBytes,
                                                    matrix,
                                                    image->width,
                                                    image->height,
                                                    filter);
        } else if (lcf.yuvaToRgbMatrixFilter != NULL) {
            libyuvResult = lcf.yuvaToRgbMatrixFilter(image->yuvPlanes[AVIF_CHAN_Y],
                                                     image->yuvRowBytes[AVIF_CHAN_Y],
                                                     image->yuvPlanes[uPlaneIndex],
                                                     image->yuvRowBytes[uPlaneIndex],
                                                     image->yuvPlanes[vPlaneIndex],
                                                     image->yuvRowBytes[vPlaneIndex],
                                                     image->alphaPlane,
                                                     image->alphaRowBytes,
                                                     rgb->pixels,
                                                     rgb->rowBytes,
                                                     matrix,
                                                     image->width,
                                                     image->height,
                                                     /*attenuate=*/0,
                                                     filter);
            *alphaReformattedWithLibYUV = AVIF_TRUE;
        } else if (lcf.yuvToRgbMatrix != NULL) {
            libyuvResult = lcf.yuvToRgbMatrix(image->yuvPlanes[AVIF_CHAN_Y],
                                              image->yuvRowBytes[AVIF_CHAN_Y],
                                              image->yuvPlanes[uPlaneIndex],
                                              image->yuvRowBytes[uPlaneIndex],
                                              image->yuvPlanes[vPlaneIndex],
                                              image->yuvRowBytes[vPlaneIndex],
                                              rgb->pixels,
                                              rgb->rowBytes,
                                              matrix,
                                              image->width,
                                              image->height);
        } else if (lcf.yuvaToRgbMatrix != NULL) {
            libyuvResult = lcf.yuvaToRgbMatrix(image->yuvPlanes[AVIF_CHAN_Y],
                                               image->yuvRowBytes[AVIF_CHAN_Y],
                                               image->yuvPlanes[uPlaneIndex],
                                               image->yuvRowBytes[uPlaneIndex],
                                               image->yuvPlanes[vPlaneIndex],
                                               image->yuvRowBytes[vPlaneIndex],
                                               image->alphaPlane,
                                               image->alphaRowBytes,
                                               rgb->pixels,
                                               rgb->rowBytes,
                                               matrix,
                                               image->width,
                                               image->height,
                                               /*attenuate=*/0);
            *alphaReformattedWithLibYUV = AVIF_TRUE;
        }
        if (inputIsHighBitDepth) {
            avifImageFreePlanes(&image8, AVIF_PLANES_ALL);
            image = NULL;
        }
    }
    return (libyuvResult != 0) ? AVIF_RESULT_REFORMAT_FAILED : AVIF_RESULT_OK;
}

//--------------------------------------------------------------------------------------------------

avifResult avifRGBImagePremultiplyAlphaLibYUV(avifRGBImage * rgb)
{
    // See if the current settings can be accomplished with libyuv, and use it (if possible).

    // The width, height, and stride parameters of libyuv functions are all of the int type.
    if (rgb->width > INT_MAX || rgb->height > INT_MAX || rgb->rowBytes > INT_MAX) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    if (rgb->depth != 8) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }

    // libavif uses byte-order when describing pixel formats, such that the R in RGBA is the lowest address,
    // similar to PNG. libyuv orders in word-order, so libavif's RGBA would be referred to in libyuv as ABGR.

    // Order of RGB doesn't matter here.
    if (rgb->format == AVIF_RGB_FORMAT_RGBA || rgb->format == AVIF_RGB_FORMAT_BGRA) {
        if (ARGBAttenuate(rgb->pixels, rgb->rowBytes, rgb->pixels, rgb->rowBytes, rgb->width, rgb->height) != 0) {
            return AVIF_RESULT_REFORMAT_FAILED;
        }
        return AVIF_RESULT_OK;
    }

    return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult avifRGBImageUnpremultiplyAlphaLibYUV(avifRGBImage * rgb)
{
    // See if the current settings can be accomplished with libyuv, and use it (if possible).

    // The width, height, and stride parameters of libyuv functions are all of the int type.
    if (rgb->width > INT_MAX || rgb->height > INT_MAX || rgb->rowBytes > INT_MAX) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    if (rgb->depth != 8) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }

    // libavif uses byte-order when describing pixel formats, such that the R in RGBA is the lowest address,
    // similar to PNG. libyuv orders in word-order, so libavif's RGBA would be referred to in libyuv as ABGR.

    if (rgb->format == AVIF_RGB_FORMAT_RGBA || rgb->format == AVIF_RGB_FORMAT_BGRA) {
        if (ARGBUnattenuate(rgb->pixels, rgb->rowBytes, rgb->pixels, rgb->rowBytes, rgb->width, rgb->height) != 0) {
            return AVIF_RESULT_REFORMAT_FAILED;
        }
        return AVIF_RESULT_OK;
    }

    return AVIF_RESULT_NOT_IMPLEMENTED;
}

avifResult avifRGBImageToF16LibYUV(avifRGBImage * rgb)
{
    // The width, height, and stride parameters of libyuv functions are all of the int type.
    if (rgb->width > INT_MAX || rgb->height > INT_MAX || rgb->rowBytes > INT_MAX) {
        return AVIF_RESULT_NOT_IMPLEMENTED;
    }
    const float scale = 1.0f / ((1 << rgb->depth) - 1);
    // Note: HalfFloatPlane requires the stride to be in bytes.
    const int result = HalfFloatPlane((const uint16_t *)rgb->pixels,
                                      rgb->rowBytes,
                                      (uint16_t *)rgb->pixels,
                                      rgb->rowBytes,
                                      scale,
                                      rgb->width * avifRGBFormatChannelCount(rgb->format),
                                      rgb->height);
    return (result == 0) ? AVIF_RESULT_OK : AVIF_RESULT_INVALID_ARGUMENT;
}

unsigned int avifLibYUVVersion(void)
{
    return (unsigned int)LIBYUV_VERSION;
}

#endif
