Use libyuv with downshift for 12/10 -> 8 bit when possible
When converting 12-bit/10-bit YUV images into 8-bit RGB, try to use
the following path first (before falling back to built-in conversion
using C code): 12/10-bit YUV -> 8-bit YUV (using libyuv) -> 8-bit
RGB (using libyuv).
This path is about ~40% faster than using the built-in conversion.
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 6ee9212..ae59f79 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -72,6 +72,10 @@
void avifArrayPop(void * arrayStruct);
void avifArrayDestroy(void * arrayStruct);
+void avifImageSetDefaults(avifImage * image);
+// Copies all fields that do not need to be freed/allocated from srcImage to dstImage.
+void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage);
+
typedef struct avifAlphaParams
{
uint32_t width;
diff --git a/src/avif.c b/src/avif.c
index 4c085f1..62eba20 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -123,7 +123,7 @@
return "Unknown";
}
-static void avifImageSetDefaults(avifImage * image)
+void avifImageSetDefaults(avifImage * image)
{
memset(image, 0, sizeof(avifImage));
image->yuvRange = AVIF_RANGE_FULL;
@@ -148,8 +148,7 @@
return avifImageCreate(0, 0, 0, AVIF_PIXEL_FORMAT_NONE);
}
-// Copies all fields that do not need to be freed/allocated from srcImage to dstImage.
-static void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage)
+void avifImageCopyNoAlloc(avifImage * dstImage, const avifImage * srcImage)
{
dstImage->width = srcImage->width;
dstImage->height = srcImage->height;
diff --git a/src/reformat_libyuv.c b/src/reformat_libyuv.c
index 580e3bd..63aa6d9 100644
--- a/src/reformat_libyuv.c
+++ b/src/reformat_libyuv.c
@@ -491,6 +491,21 @@
return AVIF_RESULT_NOT_IMPLEMENTED;
}
+// Note about the libyuv look up tables used for 8-bit 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)
+// -------------------- ------------- ------------------------------------
+// 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
+
// 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.
@@ -505,6 +520,142 @@
AVIF_FALSE, // RGB_565
};
+typedef int (*YUV400ToRGBMatrix8bpc)(const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
+typedef int (*YUVToRGBMatrixFilter8bpc)(const uint8_t *,
+ int,
+ const uint8_t *,
+ int,
+ const uint8_t *,
+ int,
+ uint8_t *,
+ int,
+ const struct YuvConstants *,
+ int,
+ int,
+ enum FilterMode);
+typedef int (*YUVToRGBMatrix8bpc)(const uint8_t *, int, const uint8_t *, int, const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
+
+// Returns AVIF_TRUE if the given yuvFormat can be converted to 8-bit RGB using libyuv, AVIF_FALSE otherwise. When AVIF_TRUE is
+// returned, exactly one of the three output function pointers will be populated with the appropriate conversion function.
+static avifBool get8bpcLibYUVConversionFunction(avifPixelFormat yuvFormat,
+ avifRGBImage * rgb,
+ YUV400ToRGBMatrix8bpc * yuv400ToRgbMatrix,
+ YUVToRGBMatrixFilter8bpc * yuvToRgbMatrixFilter,
+ YUVToRGBMatrix8bpc * yuvToRgbMatrix)
+{
+ // Lookup table for 8-bit YUV400 to 8-bit RGB Matrix.
+ static const YUV400ToRGBMatrix8bpc lutYuv400ToRgbMatrix8bpc[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 YUVToRGBMatrixFilter8bpc lutYuvToRgbMatrixFilter8bpc[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 YUV To 8-bit RGB Matrix (4:4:4 or nearest-neighbor filter).
+ static const YUVToRGBMatrix8bpc lutYuvToRgbMatrix8bpc[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
+ };
+ *yuv400ToRgbMatrix = NULL;
+ *yuvToRgbMatrixFilter = NULL;
+ *yuvToRgbMatrix = NULL;
+ if (yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
+ *yuv400ToRgbMatrix = lutYuv400ToRgbMatrix8bpc[rgb->format];
+ return *yuv400ToRgbMatrix != NULL;
+ }
+ if (yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
+ *yuvToRgbMatrixFilter = lutYuvToRgbMatrixFilter8bpc[rgb->format][yuvFormat];
+ if (*yuvToRgbMatrixFilter != NULL) {
+ return AVIF_TRUE;
+ }
+ // Only proceed with the nearest-neighbor filter if explicitly specified or left as default.
+ if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BEST_QUALITY) || (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BILINEAR)) {
+ return AVIF_FALSE;
+ }
+ }
+ *yuvToRgbMatrix = lutYuvToRgbMatrix8bpc[rgb->format][yuvFormat];
+ return *yuvToRgbMatrix != NULL;
+}
+
+// Applies the non-NULL libyuv conversion function. Exactly one of the three function pointers has to be not NULL.
+static avifResult apply8bpcLibYUVConversionFunction(const avifImage * image,
+ avifRGBImage * rgb,
+ const struct YuvConstants * matrixYUV,
+ const struct YuvConstants * matrixYVU,
+ YUV400ToRGBMatrix8bpc * yuv400ToRgbMatrix,
+ YUVToRGBMatrixFilter8bpc * yuvToRgbMatrixFilter,
+ YUVToRGBMatrix8bpc * yuvToRgbMatrix)
+{
+ avifBool isYVU = lutIsYVU[rgb->format];
+ const struct YuvConstants * matrix = isYVU ? matrixYVU : matrixYUV;
+ int libyuvResult;
+ if (*yuv400ToRgbMatrix != NULL) {
+ libyuvResult = (*yuv400ToRgbMatrix)(image->yuvPlanes[AVIF_CHAN_Y],
+ image->yuvRowBytes[AVIF_CHAN_Y],
+ rgb->pixels,
+ rgb->rowBytes,
+ matrix,
+ image->width,
+ image->height);
+ } else {
+ int uPlaneIndex = isYVU ? AVIF_CHAN_V : AVIF_CHAN_U;
+ int vPlaneIndex = isYVU ? AVIF_CHAN_U : AVIF_CHAN_V;
+ if (*yuvToRgbMatrixFilter != NULL) {
+ const enum FilterMode filter = ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) ||
+ (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST))
+ ? kFilterNone
+ : kFilterBilinear;
+ libyuvResult = (*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 {
+ libyuvResult = (*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);
+ }
+ }
+ return (libyuvResult != 0) ? AVIF_RESULT_REFORMAT_FAILED : AVIF_RESULT_OK;
+}
+
avifResult avifImageYUVToRGBLibYUV8bpc(const avifImage * image,
avifRGBImage * rgb,
const struct YuvConstants * matrixYUV,
@@ -514,139 +665,57 @@
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.
- // In addition, swapping U and V in any of these calls, along with using the Yvu matrix instead of Yuv matrix,
- // swaps B and R in these orderings as well. This table summarizes the lookup tables that follow:
- //
- // libavif format libyuv Func UV matrix (and UV argument ordering)
- // -------------------- ------------- ------------------------------------
- // 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
+ YUV400ToRGBMatrix8bpc yuv400ToRgbMatrix;
+ YUVToRGBMatrixFilter8bpc yuvToRgbMatrixFilter;
+ YUVToRGBMatrix8bpc yuvToRgbMatrix;
+ if (!get8bpcLibYUVConversionFunction(image->yuvFormat, rgb, &yuv400ToRgbMatrix, &yuvToRgbMatrixFilter, &yuvToRgbMatrix)) {
+ return AVIF_RESULT_NOT_IMPLEMENTED;
+ }
+ return apply8bpcLibYUVConversionFunction(image, rgb, matrixYUV, matrixYVU, &yuv400ToRgbMatrix, &yuvToRgbMatrixFilter, &yuvToRgbMatrix);
+}
- avifBool isYVU = lutIsYVU[rgb->format];
- const struct YuvConstants * matrix = isYVU ? matrixYVU : matrixYUV;
- if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
- // Lookup table for YUV400 to RGB Matrix.
- typedef int (*YUV400ToRGBMatrix)(const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
- YUV400ToRGBMatrix lutYuv400ToRgbMatrix[AVIF_RGB_FORMAT_COUNT] = {
- // // AVIF_RGB_FORMAT_
- NULL, // RGB
- I400ToARGBMatrix, // RGBA
- NULL, // ARGB
- NULL, // BGR
- I400ToARGBMatrix, // BGRA
- NULL, // ABGR
- NULL, // RGB_565
- };
- YUV400ToRGBMatrix yuv400ToRgbMatrix = lutYuv400ToRgbMatrix[rgb->format];
- if (yuv400ToRgbMatrix != NULL) {
- if (yuv400ToRgbMatrix(image->yuvPlanes[AVIF_CHAN_Y],
- image->yuvRowBytes[AVIF_CHAN_Y],
- rgb->pixels,
- rgb->rowBytes,
- matrix,
- image->width,
- image->height) != 0) {
- return AVIF_RESULT_REFORMAT_FAILED;
- }
- return AVIF_RESULT_OK;
- }
- } else {
- int uPlaneIndex = isYVU ? AVIF_CHAN_V : AVIF_CHAN_U;
- int vPlaneIndex = isYVU ? AVIF_CHAN_U : AVIF_CHAN_V;
- if (image->yuvFormat != AVIF_PIXEL_FORMAT_YUV444) {
- // Lookup table for YUV To RGB Matrix (with filter).
- 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);
- 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
- };
- YUVToRGBMatrixFilter yuvToRgbMatrixFilter = lutYuvToRgbMatrixFilter[rgb->format][image->yuvFormat];
- if (yuvToRgbMatrixFilter != NULL) {
- const enum FilterMode filter = ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_FASTEST) ||
- (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_NEAREST))
- ? kFilterNone
- : kFilterBilinear;
- if (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) != 0) {
- return AVIF_RESULT_REFORMAT_FAILED;
- }
- return AVIF_RESULT_OK;
- }
-
- // Only proceed with the nearest-neighbor filter if explicitly specified or left as default.
- if ((rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BEST_QUALITY) ||
- (rgb->chromaUpsampling == AVIF_CHROMA_UPSAMPLING_BILINEAR)) {
- return AVIF_RESULT_NOT_IMPLEMENTED;
- }
- }
- // Lookup table for YUV To RGB Matrix (4:4:4 or nearest-neighbor filter).
- typedef int (
- *YUVToRGBMatrix)(const uint8_t *, int, const uint8_t *, int, const uint8_t *, int, uint8_t *, int, const struct YuvConstants *, int, int);
- 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
- };
- YUVToRGBMatrix yuvToRgbMatrix = lutYuvToRgbMatrix[rgb->format][image->yuvFormat];
- if (yuvToRgbMatrix != NULL) {
- if (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) != 0) {
- return AVIF_RESULT_REFORMAT_FAILED;
- }
- return AVIF_RESULT_OK;
- }
+static avifResult avifImageDownshiftTo8bpcYUVAndConvertToRGBLibYUV(const avifImage * image,
+ avifRGBImage * rgb,
+ const struct YuvConstants * matrixYUV,
+ const struct YuvConstants * matrixYVU)
+{
+ YUV400ToRGBMatrix8bpc yuv400ToRgbMatrix;
+ YUVToRGBMatrixFilter8bpc yuvToRgbMatrixFilter;
+ YUVToRGBMatrix8bpc yuvToRgbMatrix;
+ if (!get8bpcLibYUVConversionFunction(image->yuvFormat, rgb, &yuv400ToRgbMatrix, &yuvToRgbMatrixFilter, &yuvToRgbMatrix)) {
+ return AVIF_RESULT_NOT_IMPLEMENTED;
}
- // This function didn't do anything; use the built-in YUV conversion
- return AVIF_RESULT_NOT_IMPLEMENTED;
+ avifImage image8;
+ avifImageSetDefaults(&image8);
+ avifImageCopyNoAlloc(&image8, image);
+ image8.depth = 8;
+ avifResult result = avifImageAllocatePlanes(&image8, AVIF_PLANES_YUV);
+ if (result != AVIF_RESULT_OK) {
+ return result;
+ }
+ // 16384 for 10-bit and 4096 for 12-bit.
+ const int scale = 1 << (24 - image->depth);
+ avifPixelFormatInfo pixelInfo;
+ avifGetPixelFormatInfo(image8.yuvFormat, &pixelInfo);
+ for (int plane = 0; plane < (pixelInfo.monochrome ? 1 : 3); ++plane) {
+ int planeWidth = image->width;
+ int planeHeight = image->height;
+ if (plane > 0) {
+ planeWidth = (planeWidth + pixelInfo.chromaShiftX) >> pixelInfo.chromaShiftX;
+ planeHeight = (planeHeight + pixelInfo.chromaShiftY) >> pixelInfo.chromaShiftY;
+ }
+ Convert16To8Plane((const uint16_t *)image->yuvPlanes[plane],
+ image->yuvRowBytes[plane] / 2,
+ image8.yuvPlanes[plane],
+ image8.yuvRowBytes[plane],
+ scale,
+ planeWidth,
+ planeHeight);
+ }
+ result = apply8bpcLibYUVConversionFunction(&image8, rgb, matrixYUV, matrixYVU, &yuv400ToRgbMatrix, &yuvToRgbMatrixFilter, &yuvToRgbMatrix);
+ avifImageFreePlanes(&image8, AVIF_PLANES_YUV);
+ return result;
}
avifResult avifImageYUVToRGBLibYUVHighBitDepth(const avifImage * image,
@@ -795,8 +864,9 @@
}
}
- // This function didn't do anything; use the built-in YUV conversion
- return AVIF_RESULT_NOT_IMPLEMENTED;
+ // Try to downshift the YUV pixels to 8-bit and see if it can be converted to RGB using libyuv. This is faster than using the
+ // built-in YUV conversion.
+ return avifImageDownshiftTo8bpcYUVAndConvertToRGBLibYUV(image, rgb, matrixYUV, matrixYVU);
}
//--------------------------------------------------------------------------------------------------