Add support RGB565 format
RGB565 [1] is a pixel format used by Android. It stores each pixel
in 2 bytes (with 5, 6 and 5 bits respectively for R, G and B
channels).
This is used in some older android devices which have low RAM to
minimize the size of bitmaps on those devices.
[1] https://developer.android.com/reference/android/graphics/Bitmap.Config#RGB_565
diff --git a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
index 2ad5284..6fed338 100644
--- a/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
+++ b/android_jni/avifandroidjni/src/main/jni/libavif_jni.cc
@@ -127,8 +127,9 @@
decoder.decoder->image->height);
return false;
}
- // Ensure that the bitmap format is either RGBA_8888 or RGBA_F16.
+ // Ensure that the bitmap format is RGBA_8888, RGB_565 or RGBA_F16.
if (bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888 &&
+ bitmap_info.format != ANDROID_BITMAP_FORMAT_RGB_565 &&
bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_F16) {
LOGE("Bitmap format (%d) is not supported.", bitmap_info.format);
return false;
@@ -144,6 +145,9 @@
if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGBA_F16) {
rgb_image.depth = 16;
rgb_image.isFloat = AVIF_TRUE;
+ } else if (bitmap_info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
+ rgb_image.format = AVIF_RGB_FORMAT_RGB_565;
+ rgb_image.depth = 8;
} else {
rgb_image.depth = 8;
}
diff --git a/include/avif/avif.h b/include/avif/avif.h
index 9975168..089a7e2 100644
--- a/include/avif/avif.h
+++ b/include/avif/avif.h
@@ -551,7 +551,17 @@
AVIF_RGB_FORMAT_ARGB,
AVIF_RGB_FORMAT_BGR,
AVIF_RGB_FORMAT_BGRA,
- AVIF_RGB_FORMAT_ABGR
+ AVIF_RGB_FORMAT_ABGR,
+ // RGB_565 format uses five bits for the red and blue components and six
+ // bits for the green component. Each RGB pixel is 16 bits (2 bytes), which
+ // is packed as follows:
+ // uint16_t: [r4 r3 r2 r1 r0 g5 g4 g3 g2 g1 g0 b4 b3 b2 b1 b0]
+ // r4 and r0 are the MSB and LSB of the red component respectively.
+ // g5 and g0 are the MSB and LSB of the green component respectively.
+ // b4 and b0 are the MSB and LSB of the blue component respectively.
+ // This format is only supported for YUV -> RGB conversion and when
+ // avifRGBImage.depth is set to 8.
+ AVIF_RGB_FORMAT_RGB_565
} avifRGBFormat;
AVIF_API uint32_t avifRGBFormatChannelCount(avifRGBFormat format);
AVIF_API avifBool avifRGBFormatHasAlpha(avifRGBFormat format);
diff --git a/src/avif.c b/src/avif.c
index a4a7c0b..99b1acb 100644
--- a/src/avif.c
+++ b/src/avif.c
@@ -420,7 +420,7 @@
avifBool avifRGBFormatHasAlpha(avifRGBFormat format)
{
- return (format != AVIF_RGB_FORMAT_RGB) && (format != AVIF_RGB_FORMAT_BGR);
+ return (format != AVIF_RGB_FORMAT_RGB) && (format != AVIF_RGB_FORMAT_BGR) && (format != AVIF_RGB_FORMAT_RGB_565);
}
uint32_t avifRGBFormatChannelCount(avifRGBFormat format)
@@ -430,6 +430,9 @@
uint32_t avifRGBImagePixelSize(const avifRGBImage * rgb)
{
+ if (rgb->format == AVIF_RGB_FORMAT_RGB_565) {
+ return 2;
+ }
return avifRGBFormatChannelCount(rgb->format) * ((rgb->depth > 8) ? 2 : 1);
}
diff --git a/src/reformat.c b/src/reformat.c
index fbd73da..e905331 100644
--- a/src/reformat.c
+++ b/src/reformat.c
@@ -25,6 +25,9 @@
if (rgb->isFloat && rgb->depth != 16) {
return AVIF_FALSE;
}
+ if (rgb->format == AVIF_RGB_FORMAT_RGB_565 && rgb->depth != 8) {
+ return AVIF_FALSE;
+ }
// These matrix coefficients values are currently unsupported. Revise this list as more support is added.
//
@@ -105,6 +108,16 @@
state->rgbOffsetBytesG = state->rgbChannelBytes * 2;
state->rgbOffsetBytesR = state->rgbChannelBytes * 3;
break;
+ case AVIF_RGB_FORMAT_RGB_565:
+ // Since RGB_565 consists of two bytes per RGB pixel, we simply use
+ // the pointer to the red channel to populate the entire pixel value
+ // as a uint16_t. As a result only rgbOffsetBytesR is used and the
+ // other offsets are unused.
+ state->rgbOffsetBytesR = 0;
+ state->rgbOffsetBytesG = 0;
+ state->rgbOffsetBytesB = 0;
+ state->rgbOffsetBytesA = 0;
+ break;
default:
return AVIF_FALSE;
@@ -180,7 +193,7 @@
avifResult avifImageRGBToYUV(avifImage * image, const avifRGBImage * rgb)
{
- if (!rgb->pixels) {
+ if (!rgb->pixels || rgb->format == AVIF_RGB_FORMAT_RGB_565) {
return AVIF_RESULT_REFORMAT_FAILED;
}
@@ -435,6 +448,20 @@
return AVIF_RESULT_OK;
}
+static void avifStoreRGB8Pixel(avifRGBFormat format, uint8_t R, uint8_t G, uint8_t B, uint8_t * ptrR, uint8_t * ptrG, uint8_t * ptrB)
+{
+ if (format == AVIF_RGB_FORMAT_RGB_565) {
+ // References for RGB565 color conversion:
+ // * https://docs.microsoft.com/en-us/windows/win32/directshow/working-with-16-bit-rgb
+ // * https://chromium.googlesource.com/libyuv/libyuv/+/9892d70c965678381d2a70a1c9002d1cf136ee78/source/row_common.cc#2362
+ *(uint16_t *)ptrR = (uint16_t)((B >> 3) | ((G >> 2) << 5) | ((R >> 3) << 11));
+ return;
+ }
+ *ptrR = R;
+ *ptrG = G;
+ *ptrB = B;
+}
+
// Note: This function handles alpha (un)multiply.
static avifResult avifImageYUVAnyToRGBAnySlow(const avifImage * image,
avifRGBImage * rgb,
@@ -695,9 +722,13 @@
}
if (rgb->depth == 8) {
- *ptrR = (uint8_t)(0.5f + (Rc * rgbMaxChannelF));
- *ptrG = (uint8_t)(0.5f + (Gc * rgbMaxChannelF));
- *ptrB = (uint8_t)(0.5f + (Bc * rgbMaxChannelF));
+ avifStoreRGB8Pixel(rgb->format,
+ (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
+ ptrR,
+ ptrG,
+ ptrB);
} else {
*((uint16_t *)ptrR) = (uint16_t)(0.5f + (Rc * rgbMaxChannelF));
*((uint16_t *)ptrG) = (uint16_t)(0.5f + (Gc * rgbMaxChannelF));
@@ -847,9 +878,13 @@
const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
- *ptrR = (uint8_t)(0.5f + (Rc * rgbMaxChannelF));
- *ptrG = (uint8_t)(0.5f + (Gc * rgbMaxChannelF));
- *ptrB = (uint8_t)(0.5f + (Bc * rgbMaxChannelF));
+ avifStoreRGB8Pixel(rgb->format,
+ (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
+ ptrR,
+ ptrG,
+ ptrB);
ptrR += rgbPixelBytes;
ptrG += rgbPixelBytes;
@@ -891,9 +926,13 @@
const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
- *ptrR = (uint8_t)(0.5f + (Rc * rgbMaxChannelF));
- *ptrG = (uint8_t)(0.5f + (Gc * rgbMaxChannelF));
- *ptrB = (uint8_t)(0.5f + (Bc * rgbMaxChannelF));
+ avifStoreRGB8Pixel(rgb->format,
+ (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
+ ptrR,
+ ptrG,
+ ptrB);
ptrR += rgbPixelBytes;
ptrG += rgbPixelBytes;
@@ -1001,9 +1040,7 @@
uint8_t * ptrB = &rgb->pixels[state->rgbOffsetBytesB + (j * rgb->rowBytes)];
for (uint32_t i = 0; i < image->width; ++i) {
- *ptrR = ptrV[i];
- *ptrG = ptrY[i];
- *ptrB = ptrU[i];
+ avifStoreRGB8Pixel(rgb->format, ptrV[i], ptrY[i], ptrU[i], ptrR, ptrG, ptrB);
ptrR += rgbPixelBytes;
ptrG += rgbPixelBytes;
@@ -1047,9 +1084,13 @@
const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
- *ptrR = (uint8_t)(0.5f + (Rc * rgbMaxChannelF));
- *ptrG = (uint8_t)(0.5f + (Gc * rgbMaxChannelF));
- *ptrB = (uint8_t)(0.5f + (Bc * rgbMaxChannelF));
+ avifStoreRGB8Pixel(rgb->format,
+ (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
+ ptrR,
+ ptrG,
+ ptrB);
ptrR += rgbPixelBytes;
ptrG += rgbPixelBytes;
@@ -1087,9 +1128,13 @@
const float Gc = AVIF_CLAMP(G, 0.0f, 1.0f);
const float Bc = AVIF_CLAMP(B, 0.0f, 1.0f);
- *ptrR = (uint8_t)(0.5f + (Rc * rgbMaxChannelF));
- *ptrG = (uint8_t)(0.5f + (Gc * rgbMaxChannelF));
- *ptrB = (uint8_t)(0.5f + (Bc * rgbMaxChannelF));
+ avifStoreRGB8Pixel(rgb->format,
+ (uint8_t)(0.5f + (Rc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Gc * rgbMaxChannelF)),
+ (uint8_t)(0.5f + (Bc * rgbMaxChannelF)),
+ ptrR,
+ ptrG,
+ ptrB);
ptrR += rgbPixelBytes;
ptrG += rgbPixelBytes;
diff --git a/src/reformat_libyuv.c b/src/reformat_libyuv.c
index 10a6e4e..f07c75f 100644
--- a/src/reformat_libyuv.c
+++ b/src/reformat_libyuv.c
@@ -359,14 +359,15 @@
// 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 this block's intent:
//
- // libavif format libyuv Func UV matrix (and UV argument ordering)
- // -------------------- ------------- ------------------------------------
- // AVIF_RGB_FORMAT_RGB *ToRGB24Matrix matrixYVU
- // AVIF_RGB_FORMAT_BGR *ToRGB24Matrix matrixYUV
- // AVIF_RGB_FORMAT_BGRA *ToARGBMatrix matrixYUV
- // AVIF_RGB_FORMAT_RGBA *ToARGBMatrix matrixYVU
- // AVIF_RGB_FORMAT_ABGR *ToRGBAMatrix matrixYUV
- // AVIF_RGB_FORMAT_ARGB *ToRGBAMatrix matrixYVU
+ // libavif format libyuv Func UV matrix (and UV argument ordering)
+ // -------------------- ------------- ------------------------------------
+ // AVIF_RGB_FORMAT_RGB *ToRGB24Matrix matrixYVU
+ // AVIF_RGB_FORMAT_BGR *ToRGB24Matrix matrixYUV
+ // AVIF_RGB_FORMAT_RGB_565 *ToRGB565Matrix matrixYUV
+ // AVIF_RGB_FORMAT_BGRA *ToARGBMatrix matrixYUV
+ // AVIF_RGB_FORMAT_RGBA *ToARGBMatrix matrixYVU
+ // AVIF_RGB_FORMAT_ABGR *ToRGBAMatrix matrixYUV
+ // AVIF_RGB_FORMAT_ARGB *ToRGBAMatrix matrixYVU
if (rgb->format == AVIF_RGB_FORMAT_RGB) {
// AVIF_RGB_FORMAT_RGB *ToRGB24Matrix matrixYVU
@@ -406,6 +407,25 @@
}
return AVIF_RESULT_OK;
}
+ } else if (rgb->format == AVIF_RGB_FORMAT_RGB_565) {
+ // AVIF_RGB_FORMAT_BGR *ToRGB565Matrix matrixYUV
+
+ if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) {
+ if (I420ToRGB565Matrix(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],
+ rgb->pixels,
+ rgb->rowBytes,
+ matrixYUV,
+ image->width,
+ image->height) != 0) {
+ return AVIF_RESULT_REFORMAT_FAILED;
+ }
+ return AVIF_RESULT_OK;
+ }
} else if (rgb->format == AVIF_RGB_FORMAT_BGRA) {
// AVIF_RGB_FORMAT_BGRA *ToARGBMatrix matrixYUV
diff --git a/tests/avifyuv.c b/tests/avifyuv.c
index 76ac5bd..0c1a53a 100644
--- a/tests/avifyuv.c
+++ b/tests/avifyuv.c
@@ -39,6 +39,8 @@
return "BGRA";
case AVIF_RGB_FORMAT_ABGR:
return "ABGR";
+ case AVIF_RGB_FORMAT_RGB_565:
+ return "RGB_565";
}
return "Unknown";
}