#include "avif/internal.h"
#if !defined(AVIF_LIBYUV_ENABLED)
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"
#pragma clang diagnostic ignored "-Wnewline-eof"
#endif
#include <libyuv.h>
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#if defined(__clang__)
#define IGNORE_CFI_ICALL __attribute__((no_sanitize("cfi-icall")))
#else
#define IGNORE_CFI_ICALL
#endif
#if LIBYUV_VERSION < 1844
#define I444ToRGB24Matrix NULL
#define I422ToRGB24MatrixFilter NULL
#endif
#if LIBYUV_VERSION < 1841
#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
#endif
#if LIBYUV_VERSION < 1771
#define I422AlphaToARGBMatrix NULL
#define I444AlphaToARGBMatrix NULL
#endif
#if LIBYUV_VERSION < 1756
#define I400ToARGBMatrix NULL
#endif
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)
{
avifPixelFormatInfo format_info;
avifGetPixelFormatInfo(dst_format, &format_info);
const int min_num_rows = (format_info.chromaShiftY == 1) ? 2 : 1;
uint8_t * src_argb;
const int src_stride_argb = width * 4;
const int soft_allocation_limit = 16384; int num_allocated_rows;
if ((height == 1) || ((int64_t)src_stride_argb * height <= soft_allocation_limit)) {
num_allocated_rows = height;
} else {
if ((int64_t)src_stride_argb * min_num_rows > INT_MAX) {
return -1;
}
num_allocated_rows = AVIF_MAX(1, soft_allocation_limit / (src_stride_argb * min_num_rows)) * min_num_rows;
}
src_argb = 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;
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 < 1840
AVIF_DEFINE_CONVERSION(ABGRToJ422, ABGRToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(ABGRToJ420, ABGRToARGB, ARGBToJ420, AVIF_PIXEL_FORMAT_YUV420)
#endif
AVIF_DEFINE_CONVERSION(avifRAWToI444, RAWToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifRAWToI422, RAWToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifRAWToJ422, RAWToARGB, ARGBToJ422, AVIF_PIXEL_FORMAT_YUV422)
AVIF_DEFINE_CONVERSION(avifABGRToI444, ABGRToARGB, ARGBToI444, AVIF_PIXEL_FORMAT_YUV444)
AVIF_DEFINE_CONVERSION(avifABGRToI422, ABGRToARGB, ARGBToI422, AVIF_PIXEL_FORMAT_YUV422)
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_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_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)
static avifResult avifImageRGBToYUVLibYUV8bpc(avifImage * image, const avifRGBImage * rgb);
avifResult avifImageRGBToYUVLibYUV(avifImage * image, const avifRGBImage * rgb)
{
if ((image->depth == 8) && (rgb->depth == 8)) {
return avifImageRGBToYUVLibYUV8bpc(image, rgb);
}
return AVIF_RESULT_NOT_IMPLEMENTED;
}
avifResult avifImageRGBToYUVLibYUV8bpc(avifImage * image, const avifRGBImage * rgb)
{
assert((image->depth == 8) && (rgb->depth == 8));
if ((image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT470BG) || (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT601)) {
if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
typedef int (*RGBtoY)(const uint8_t *, int, uint8_t *, int, int, int);
RGBtoY lutRgbToY[2][AVIF_RGB_FORMAT_COUNT] = { {
NULL, NULL, NULL, NULL, ARGBToI400, NULL, NULL, },
{
RAWToJ400, ABGRToJ400, NULL, RGB24ToJ400, ARGBToJ400, RGBAToJ400, NULL }
};
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 {
typedef int (*RGBtoYUV)(const uint8_t *, int, uint8_t *, int, uint8_t *, int, uint8_t *, int, int, int);
RGBtoYUV lutRgbToYuv[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{
{ NULL, avifRAWToI444, avifRAWToI422, RAWToI420, NULL }, { NULL, avifABGRToI444, avifABGRToI422, ABGRToI420, NULL }, { NULL, avifBGRAToI444, avifBGRAToI422, BGRAToI420, NULL }, { NULL, avifRGB24ToI444, avifRGB24ToI422, RGB24ToI420, NULL }, { NULL, ARGBToI444, ARGBToI422, ARGBToI420, NULL }, { NULL, avifRGBAToI444, avifRGBAToI422, RGBAToI420, NULL }, { NULL, NULL, NULL, NULL, NULL } },
{
{ NULL, NULL, avifRAWToJ422, RAWToJ420, NULL }, { NULL, NULL, ABGRToJ422, ABGRToJ420, NULL }, { NULL, NULL, avifBGRAToJ422, avifBGRAToJ420, NULL }, { NULL, NULL, avifRGB24ToJ422, RGB24ToJ420, NULL }, { NULL, NULL, ARGBToJ422, ARGBToJ420, NULL }, { NULL, NULL, avifRGBAToJ422, avifRGBAToJ420, NULL }, { NULL, NULL, NULL, NULL, NULL } }
};
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;
}
}
}
return AVIF_RESULT_NOT_IMPLEMENTED;
}
static const avifBool lutIsYVU[AVIF_RGB_FORMAT_COUNT] = {
AVIF_TRUE, AVIF_TRUE, AVIF_TRUE, AVIF_FALSE, AVIF_FALSE, AVIF_FALSE, AVIF_FALSE, };
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);
typedef struct
{
YUV400ToRGBMatrix yuv400ToRgbMatrix;
YUVToRGBMatrixFilter yuvToRgbMatrixFilter;
YUVAToRGBMatrixFilter yuvaToRgbMatrixFilter;
YUVToRGBMatrix yuvToRgbMatrix;
YUVAToRGBMatrix yuvaToRgbMatrix;
YUVToRGBMatrixFilterHighBitDepth yuvToRgbMatrixFilterHighBitDepth;
YUVAToRGBMatrixFilterHighBitDepth yuvaToRgbMatrixFilterHighBitDepth;
YUVToRGBMatrixHighBitDepth yuvToRgbMatrixHighBitDepth;
YUVAToRGBMatrixHighBitDepth yuvaToRgbMatrixHighBitDepth;
} LibyuvConversionFunction;
static avifBool nearestNeighborFilterAllowed(int chromaUpsampling)
{
return chromaUpsampling != AVIF_CHROMA_UPSAMPLING_BILINEAR && chromaUpsampling != AVIF_CHROMA_UPSAMPLING_BEST_QUALITY;
}
static avifBool getLibYUVConversionFunction(avifPixelFormat yuvFormat,
int yuvDepth,
avifRGBImage * rgb,
avifBool alphaPreferred,
LibyuvConversionFunction * lcf)
{
static const YUV400ToRGBMatrix lutYuv400ToRgbMatrix[AVIF_RGB_FORMAT_COUNT] = {
NULL, I400ToARGBMatrix, NULL, NULL, I400ToARGBMatrix, NULL, NULL, };
static const YUVToRGBMatrixFilter lutYuvToRgbMatrixFilter[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{ NULL, NULL, I422ToRGB24MatrixFilter, I420ToRGB24MatrixFilter, NULL }, { NULL, NULL, I422ToARGBMatrixFilter, I420ToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I422ToRGB24MatrixFilter, I420ToRGB24MatrixFilter, NULL }, { NULL, NULL, I422ToARGBMatrixFilter, I420ToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, };
static const YUVAToRGBMatrixFilter lutYuvaToRgbMatrixFilter[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I422AlphaToARGBMatrixFilter, I420AlphaToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I422AlphaToARGBMatrixFilter, I420AlphaToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, };
static const YUVToRGBMatrix lutYuvToRgbMatrix[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{ NULL, I444ToRGB24Matrix, NULL, I420ToRGB24Matrix, NULL }, { NULL, I444ToARGBMatrix, I422ToARGBMatrix, I420ToARGBMatrix, NULL }, { NULL, NULL, I422ToRGBAMatrix, I420ToRGBAMatrix, NULL }, { NULL, I444ToRGB24Matrix, NULL, I420ToRGB24Matrix, NULL }, { NULL, I444ToARGBMatrix, I422ToARGBMatrix, I420ToARGBMatrix, NULL }, { NULL, NULL, I422ToRGBAMatrix, I420ToRGBAMatrix, NULL }, { NULL, NULL, I422ToRGB565Matrix, I420ToRGB565Matrix, NULL }, };
static const YUVAToRGBMatrix lutYuvaToRgbMatrix[AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{ NULL, NULL, NULL, NULL, NULL }, { NULL, I444AlphaToARGBMatrix, I422AlphaToARGBMatrix, I420AlphaToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, I444AlphaToARGBMatrix, I422AlphaToARGBMatrix, I420AlphaToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, };
static const YUVToRGBMatrixFilterHighBitDepth lutYuvToRgbMatrixFilterHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I210ToARGBMatrixFilter, I010ToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I210ToARGBMatrixFilter, I010ToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
};
static const YUVAToRGBMatrixFilterHighBitDepth lutYuvaToRgbMatrixFilterHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I210AlphaToARGBMatrixFilter, I010AlphaToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, I210AlphaToARGBMatrixFilter, I010AlphaToARGBMatrixFilter, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
};
static const YUVToRGBMatrixHighBitDepth lutYuvToRgbMatrixHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, I410ToARGBMatrix, I210ToARGBMatrix, I010ToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, I410ToARGBMatrix, I210ToARGBMatrix, I010ToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, I012ToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, I012ToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
};
static const YUVAToRGBMatrixHighBitDepth lutYuvaToRgbMatrixHighBitDepth[2][AVIF_RGB_FORMAT_COUNT][AVIF_PIXEL_FORMAT_COUNT] = {
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, I410AlphaToARGBMatrix, I210AlphaToARGBMatrix, I010AlphaToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, I410AlphaToARGBMatrix, I210AlphaToARGBMatrix, I010AlphaToARGBMatrix, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
{
{ NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, },
};
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;
}
}
}
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)
{
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) {
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;
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 { 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;
assert(!downshiftAlpha || image->alphaPlane);
AVIF_CHECKRES(avifImageAllocatePlanes(image8, downshiftAlpha ? AVIF_PLANES_ALL : AVIF_PLANES_YUV));
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;
if (rgb->depth != 8 || (image->depth != 8 && image->depth != 10 && image->depth != 12)) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
const struct YuvConstants * matrixYUV = NULL;
const struct YuvConstants * matrixYVU = NULL;
getLibYUVConstants(image, &matrixYUV, &matrixYVU);
if (!matrixYVU) {
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) {
*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,
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,
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,
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,
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)
{
if (rgb->depth != 8) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
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)
{
if (rgb->depth != 8) {
return AVIF_RESULT_NOT_IMPLEMENTED;
}
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)
{
const float scale = 1.0f / ((1 << rgb->depth) - 1);
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