Merge pull request #1008 from vigneshvg/cl_rgb565

Add support RGB565 format
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 63f1bf6..ed22c52 100644
--- a/src/reformat_libyuv.c
+++ b/src/reformat_libyuv.c
@@ -365,14 +365,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
@@ -412,6 +413,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";
 }