Large optimizations to RGB to YUV conversion, exposed utils
* Split avifImageYUVToRGB into faster internal functions (estimated gain: 3.5x)
* exposed util functions: avifFullToLimitedY, avifFullToLimitedUV, avifLimitedToFullY, avifLimitedToFullUV, avifPrepareReformatState
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e982e4..a647d25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,12 @@
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+### Added
+- exposed util functions: `avifFullToLimitedY`, `avifFullToLimitedUV`, `avifLimitedToFullY`, `avifLimitedToFullUV`, `avifPrepareReformatState`
+
### Changed
- Renamed ispeWidth/ispeHeight to containerWidth/containerHeight; they now can hold tkhd's width/height
+- Split avifImageYUVToRGB into faster internal functions (estimated gain: 3.5x)
## [0.3.11] - 2019-09-26
### Added
diff --git a/include/avif/avif.h b/include/avif/avif.h
index e54e45f..857e70d 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -313,6 +313,29 @@
avifResult avifImageRGBToYUV(avifImage * image);
avifResult avifImageYUVToRGB(avifImage * image);
+// ---------------------------------------------------------------------------
+// YUV Utils
+
+int avifFullToLimitedY(int depth, int v);
+int avifFullToLimitedUV(int depth, int v);
+int avifLimitedToFullY(int depth, int v);
+int avifLimitedToFullUV(int depth, int v);
+
+typedef struct avifReformatState
+{
+ // YUV coefficients
+ float kr;
+ float kg;
+ float kb;
+
+ avifPixelFormatInfo formatInfo;
+ avifBool usesU16;
+} avifReformatState;
+avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state);
+
+// ---------------------------------------------------------------------------
+// avifDecoder
+
// Useful stats related to a read/write
typedef struct avifIOStats
{
@@ -418,7 +441,10 @@
avifBool avifDecoderIsKeyframe(avifDecoder * decoder, uint32_t frameIndex);
uint32_t avifDecoderNearestKeyframe(avifDecoder * decoder, uint32_t frameIndex);
-// avifEncoder notes:
+// ---------------------------------------------------------------------------
+// avifEncoder
+
+// Notes:
// * if avifEncoderWrite() returns AVIF_RESULT_OK, output must be freed with avifRWDataFree()
// * if (maxThreads < 2), multithreading is disabled
// * quality range: [AVIF_BEST_QUALITY - AVIF_WORST_QUALITY]
diff --git a/include/avif/internal.h b/include/avif/internal.h
index 6ce2689..412a1ea 100644
--- a/include/avif/internal.h
+++ b/include/avif/internal.h
@@ -36,11 +36,6 @@
uint64_t avifHTON64(uint64_t l);
uint64_t avifNTOH64(uint64_t l);
-int avifFullToLimitedY(int depth, int v);
-int avifFullToLimitedUV(int depth, int v);
-int avifLimitedToFullY(int depth, int v);
-int avifLimitedToFullUV(int depth, int v);
-
void avifCalcYUVCoefficients(avifImage * image, float * outR, float * outG, float * outB);
#define AVIF_ARRAY_DECLARE(TYPENAME, ITEMSTYPE, ITEMSNAME) \
diff --git a/src/reformat.c b/src/reformat.c
index f0bbd15..bf71940 100644
--- a/src/reformat.c
+++ b/src/reformat.c
@@ -5,17 +5,6 @@
#include <string.h>
-typedef struct avifReformatState
-{
- // YUV coefficients
- float kr;
- float kg;
- float kb;
-
- avifPixelFormatInfo formatInfo;
- avifBool usesU16;
-} avifReformatState;
-
struct YUVBlock
{
float y;
@@ -23,7 +12,7 @@
float v;
};
-static avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state)
+avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state)
{
if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
return AVIF_FALSE;
@@ -196,6 +185,198 @@
return AVIF_RESULT_OK;
}
+static avifResult avifImageYUVToRGB16Color(avifImage * image, avifReformatState * state)
+{
+ const float kr = state->kr;
+ const float kg = state->kg;
+ const float kb = state->kb;
+
+ float maxChannel = (float)((1 << image->depth) - 1);
+ uint8_t ** rgbPlanes = image->rgbPlanes;
+ uint32_t * rgbRowBytes = image->rgbRowBytes;
+ for (uint32_t j = 0; j < image->height; ++j) {
+ const int uvJ = j >> state->formatInfo.chromaShiftY;
+ uint16_t * ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
+ uint16_t * ptrU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
+ uint16_t * ptrV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
+ uint16_t * ptrR = (uint16_t *)&rgbPlanes[AVIF_CHAN_R][j * rgbRowBytes[AVIF_CHAN_R]];
+ uint16_t * ptrG = (uint16_t *)&rgbPlanes[AVIF_CHAN_G][j * rgbRowBytes[AVIF_CHAN_G]];
+ uint16_t * ptrB = (uint16_t *)&rgbPlanes[AVIF_CHAN_B][j * rgbRowBytes[AVIF_CHAN_B]];
+
+ for (uint32_t i = 0; i < image->width; ++i) {
+ // Unpack YUV into unorm
+ const int uvI = i >> state->formatInfo.chromaShiftX;
+ uint32_t unormY = ptrY[i];
+ uint32_t unormU = ptrU[uvI];
+ uint32_t unormV = ptrV[uvI];
+
+ // adjust for limited/full color range, if need be
+ if (image->yuvRange == AVIF_RANGE_LIMITED) {
+ unormY = avifLimitedToFullY(image->depth, unormY);
+ unormU = avifLimitedToFullUV(image->depth, unormU);
+ unormV = avifLimitedToFullUV(image->depth, unormV);
+ }
+
+ // Convert unorm to float
+ const float Y = unormY / maxChannel;
+ const float Cb = (unormU / maxChannel) - 0.5f;
+ const float Cr = (unormV / maxChannel) - 0.5f;
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+ R = AVIF_CLAMP(R, 0.0f, 1.0f);
+ G = AVIF_CLAMP(G, 0.0f, 1.0f);
+ B = AVIF_CLAMP(B, 0.0f, 1.0f);
+
+ ptrR[i] = (uint16_t)(0.5f + (R * maxChannel));
+ ptrG[i] = (uint16_t)(0.5f + (G * maxChannel));
+ ptrB[i] = (uint16_t)(0.5f + (B * maxChannel));
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
+static avifResult avifImageYUVToRGB16Mono(avifImage * image, avifReformatState * state)
+{
+ const float kr = state->kr;
+ const float kg = state->kg;
+ const float kb = state->kb;
+
+ float maxChannel = (float)((1 << image->depth) - 1);
+ uint8_t ** rgbPlanes = image->rgbPlanes;
+ uint32_t * rgbRowBytes = image->rgbRowBytes;
+ for (uint32_t j = 0; j < image->height; ++j) {
+ uint16_t * ptrY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
+ uint16_t * ptrR = (uint16_t *)&rgbPlanes[AVIF_CHAN_R][j * rgbRowBytes[AVIF_CHAN_R]];
+ uint16_t * ptrG = (uint16_t *)&rgbPlanes[AVIF_CHAN_G][j * rgbRowBytes[AVIF_CHAN_G]];
+ uint16_t * ptrB = (uint16_t *)&rgbPlanes[AVIF_CHAN_B][j * rgbRowBytes[AVIF_CHAN_B]];
+
+ for (uint32_t i = 0; i < image->width; ++i) {
+ // Unpack YUV into unorm
+ uint32_t unormY = ptrY[i];
+
+ // adjust for limited/full color range, if need be
+ if (image->yuvRange == AVIF_RANGE_LIMITED) {
+ unormY = avifLimitedToFullY(image->depth, unormY);
+ }
+
+ // Convert unorm to float
+ const float Y = unormY / maxChannel;
+ const float Cb = -0.5f;
+ const float Cr = -0.5f;
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+ R = AVIF_CLAMP(R, 0.0f, 1.0f);
+ G = AVIF_CLAMP(G, 0.0f, 1.0f);
+ B = AVIF_CLAMP(B, 0.0f, 1.0f);
+
+ ptrR[i] = (uint16_t)(0.5f + (R * maxChannel));
+ ptrG[i] = (uint16_t)(0.5f + (G * maxChannel));
+ ptrB[i] = (uint16_t)(0.5f + (B * maxChannel));
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
+static avifResult avifImageYUVToRGB8Color(avifImage * image, avifReformatState * state)
+{
+ const float kr = state->kr;
+ const float kg = state->kg;
+ const float kb = state->kb;
+
+ float maxChannel = (float)((1 << image->depth) - 1);
+ uint8_t ** rgbPlanes = image->rgbPlanes;
+ uint32_t * rgbRowBytes = image->rgbRowBytes;
+ for (uint32_t j = 0; j < image->height; ++j) {
+ int uvJ = j >> state->formatInfo.chromaShiftY;
+ uint8_t * ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
+ uint8_t * ptrU = &image->yuvPlanes[AVIF_CHAN_U][(uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
+ uint8_t * ptrV = &image->yuvPlanes[AVIF_CHAN_V][(uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
+ uint8_t * ptrR = &rgbPlanes[AVIF_CHAN_R][j * rgbRowBytes[AVIF_CHAN_R]];
+ uint8_t * ptrG = &rgbPlanes[AVIF_CHAN_G][j * rgbRowBytes[AVIF_CHAN_G]];
+ uint8_t * ptrB = &rgbPlanes[AVIF_CHAN_B][j * rgbRowBytes[AVIF_CHAN_B]];
+
+ for (uint32_t i = 0; i < image->width; ++i) {
+ // Unpack YUV into unorm
+ const int uvI = i >> state->formatInfo.chromaShiftX;
+ uint32_t unormY = ptrY[i];
+ uint32_t unormU = ptrU[uvI];
+ uint32_t unormV = ptrV[uvI];
+
+ // adjust for limited/full color range, if need be
+ if (image->yuvRange == AVIF_RANGE_LIMITED) {
+ unormY = avifLimitedToFullY(image->depth, unormY);
+ unormU = avifLimitedToFullUV(image->depth, unormU);
+ unormV = avifLimitedToFullUV(image->depth, unormV);
+ }
+
+ // Convert unorm to float
+ const float Y = unormY / maxChannel;
+ const float Cb = (unormU / maxChannel) - 0.5f;
+ const float Cr = (unormV / maxChannel) - 0.5f;
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+ R = AVIF_CLAMP(R, 0.0f, 1.0f);
+ G = AVIF_CLAMP(G, 0.0f, 1.0f);
+ B = AVIF_CLAMP(B, 0.0f, 1.0f);
+
+ ptrR[i] = (uint8_t)(0.5f + (R * maxChannel));
+ ptrG[i] = (uint8_t)(0.5f + (G * maxChannel));
+ ptrB[i] = (uint8_t)(0.5f + (B * maxChannel));
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
+static avifResult avifImageYUVToRGB8Mono(avifImage * image, avifReformatState * state)
+{
+ const float kr = state->kr;
+ const float kg = state->kg;
+ const float kb = state->kb;
+
+ float maxChannel = (float)((1 << image->depth) - 1);
+ uint8_t ** rgbPlanes = image->rgbPlanes;
+ uint32_t * rgbRowBytes = image->rgbRowBytes;
+ for (uint32_t j = 0; j < image->height; ++j) {
+ uint8_t * ptrY = &image->yuvPlanes[AVIF_CHAN_Y][(j * image->yuvRowBytes[AVIF_CHAN_Y])];
+ uint8_t * ptrR = &rgbPlanes[AVIF_CHAN_R][j * rgbRowBytes[AVIF_CHAN_R]];
+ uint8_t * ptrG = &rgbPlanes[AVIF_CHAN_G][j * rgbRowBytes[AVIF_CHAN_G]];
+ uint8_t * ptrB = &rgbPlanes[AVIF_CHAN_B][j * rgbRowBytes[AVIF_CHAN_B]];
+
+ for (uint32_t i = 0; i < image->width; ++i) {
+ // Unpack YUV into unorm
+ uint32_t unormY = ptrY[i];
+
+ // adjust for limited/full color range, if need be
+ if (image->yuvRange == AVIF_RANGE_LIMITED) {
+ unormY = avifLimitedToFullY(image->depth, unormY);
+ }
+
+ // Convert unorm to float
+ const float Y = unormY / maxChannel;
+ const float Cb = -0.5f;
+ const float Cr = -0.5f;
+
+ float R = Y + (2 * (1 - kr)) * Cr;
+ float B = Y + (2 * (1 - kb)) * Cb;
+ float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
+ R = AVIF_CLAMP(R, 0.0f, 1.0f);
+ G = AVIF_CLAMP(G, 0.0f, 1.0f);
+ B = AVIF_CLAMP(B, 0.0f, 1.0f);
+
+ ptrR[i] = (uint8_t)(0.5f + (R * maxChannel));
+ ptrG[i] = (uint8_t)(0.5f + (G * maxChannel));
+ ptrB[i] = (uint8_t)(0.5f + (B * maxChannel));
+ }
+ }
+ return AVIF_RESULT_OK;
+}
+
avifResult avifImageYUVToRGB(avifImage * image)
{
if (!image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || !image->yuvPlanes[AVIF_CHAN_V]) {
@@ -209,88 +390,17 @@
avifImageAllocatePlanes(image, AVIF_PLANES_RGB);
- const float kr = state.kr;
- const float kg = state.kg;
- const float kb = state.kb;
-
- int yuvUNorm[3];
- float yuvPixel[3];
- float rgbPixel[3];
- float maxChannel = (float)((1 << image->depth) - 1);
- uint8_t ** rgbPlanes = image->rgbPlanes;
- uint32_t * rgbRowBytes = image->rgbRowBytes;
- for (uint32_t j = 0; j < image->height; ++j) {
- for (uint32_t i = 0; i < image->width; ++i) {
- // Unpack YUV into unorm
- int uvI = i >> state.formatInfo.chromaShiftX;
- int uvJ = j >> state.formatInfo.chromaShiftY;
- if (state.usesU16) {
- yuvUNorm[0] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(i * 2) + (j * image->yuvRowBytes[AVIF_CHAN_Y])]);
- if (image->yuvRowBytes[AVIF_CHAN_U]) {
- yuvUNorm[1] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])]);
- } else {
- yuvUNorm[1] = 0;
- }
- if (image->yuvRowBytes[AVIF_CHAN_V]) {
- yuvUNorm[2] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])]);
- } else {
- yuvUNorm[2] = 0;
- }
- } else {
- yuvUNorm[0] = image->yuvPlanes[AVIF_CHAN_Y][i + (j * image->yuvRowBytes[AVIF_CHAN_Y])];
- if (image->yuvRowBytes[AVIF_CHAN_U]) {
- yuvUNorm[1] = image->yuvPlanes[AVIF_CHAN_U][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])];
- } else {
- yuvUNorm[1] = 0;
- }
- if (image->yuvRowBytes[AVIF_CHAN_V]) {
- yuvUNorm[2] = image->yuvPlanes[AVIF_CHAN_V][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])];
- } else {
- yuvUNorm[2] = 0;
- }
- }
-
- // adjust for limited/full color range, if need be
- if (image->yuvRange == AVIF_RANGE_LIMITED) {
- yuvUNorm[0] = avifLimitedToFullY(image->depth, yuvUNorm[0]);
- yuvUNorm[1] = avifLimitedToFullUV(image->depth, yuvUNorm[1]);
- yuvUNorm[2] = avifLimitedToFullUV(image->depth, yuvUNorm[2]);
- }
-
- // Convert unorm to float
- yuvPixel[0] = yuvUNorm[0] / maxChannel;
- yuvPixel[1] = yuvUNorm[1] / maxChannel;
- yuvPixel[2] = yuvUNorm[2] / maxChannel;
- yuvPixel[1] -= 0.5f;
- yuvPixel[2] -= 0.5f;
-
- float Y = yuvPixel[0];
- float Cb = yuvPixel[1];
- float Cr = yuvPixel[2];
-
- float R = Y + (2 * (1 - kr)) * Cr;
- float B = Y + (2 * (1 - kb)) * Cb;
- float G = Y - ((2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) / kg);
-
- rgbPixel[0] = AVIF_CLAMP(R, 0.0f, 1.0f);
- rgbPixel[1] = AVIF_CLAMP(G, 0.0f, 1.0f);
- rgbPixel[2] = AVIF_CLAMP(B, 0.0f, 1.0f);
-
- if (state.usesU16) {
- uint16_t * pR = (uint16_t *)&rgbPlanes[AVIF_CHAN_R][(i * 2) + (j * rgbRowBytes[AVIF_CHAN_R])];
- *pR = (uint16_t)avifRoundf(rgbPixel[0] * maxChannel);
- uint16_t * pG = (uint16_t *)&rgbPlanes[AVIF_CHAN_G][(i * 2) + (j * rgbRowBytes[AVIF_CHAN_G])];
- *pG = (uint16_t)avifRoundf(rgbPixel[1] * maxChannel);
- uint16_t * pB = (uint16_t *)&rgbPlanes[AVIF_CHAN_B][(i * 2) + (j * rgbRowBytes[AVIF_CHAN_B])];
- *pB = (uint16_t)avifRoundf(rgbPixel[2] * maxChannel);
- } else {
- image->rgbPlanes[AVIF_CHAN_R][i + (j * image->rgbRowBytes[AVIF_CHAN_R])] = (uint8_t)avifRoundf(rgbPixel[0] * maxChannel);
- image->rgbPlanes[AVIF_CHAN_G][i + (j * image->rgbRowBytes[AVIF_CHAN_G])] = (uint8_t)avifRoundf(rgbPixel[1] * maxChannel);
- image->rgbPlanes[AVIF_CHAN_B][i + (j * image->rgbRowBytes[AVIF_CHAN_B])] = (uint8_t)avifRoundf(rgbPixel[2] * maxChannel);
- }
+ if (state.usesU16) {
+ if (image->yuvRowBytes[AVIF_CHAN_U] && image->yuvRowBytes[AVIF_CHAN_V]) {
+ return avifImageYUVToRGB16Color(image, &state);
}
+ return avifImageYUVToRGB16Mono(image, &state);
}
- return AVIF_RESULT_OK;
+
+ if (image->yuvRowBytes[AVIF_CHAN_U] && image->yuvRowBytes[AVIF_CHAN_V]) {
+ return avifImageYUVToRGB8Color(image, &state);
+ }
+ return avifImageYUVToRGB8Mono(image, &state);
}
int avifLimitedToFullY(int depth, int v)