title: JNI YUV_420_888 to RGBA_8888 conversion
body: <p>Currently I'm building an app to do real-time image processing and then display. The first step is to try displaying the original preview using Camera2 API and ANativeWindow API. I pass the y, u, v channels through JNI separately and do YUV2RGB conversion following the <a href="https://en.wikipedia.org/wiki/YUV" rel="nofollow noreferrer">Wikipedia article</a>,  but  got wrong color output running on <em>Google Pixel - 7.1.0 - API 25 - 1080x1920</em> on <em>Genymotion</em>:</p>
  
  <p><a href="https://i.stack.imgur.com/Ql9gd.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Ql9gd.jpg" alt="enter image description here"></a></p>
  
  <p>Implementation of <code>ImageReader.OnImageAvailableListener</code> :</p>
  
  <pre><code>private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
  
      @Override
      public void onImageAvailable(ImageReader reader) {
          // get the newest frame
          Image image = reader.acquireNextImage();
  
          if (image == null) {
              return;
          }
  
          Image.Plane Y_plane = image.getPlanes()[0];
          int Y_rowStride = Y_plane.getRowStride();
          Image.Plane U_plane = image.getPlanes()[1];
          int U_rowStride = U_plane.getRowStride();
          Image.Plane V_plane = image.getPlanes()[2];
          int V_rowStride = V_plane.getRowStride();
          JNIUtils.RGBADisplay(image.getWidth(), image.getHeight(), Y_rowStride, Y_plane.getBuffer(), U_rowStride, U_plane.getBuffer(), V_rowStride, V_plane.getBuffer(), surface);
          image.close();
      }
  };
  </code></pre>
  
  <p>JNI:</p>
  
  <pre><code>public static native void RGBADisplay(int srcWidth, int srcHeight, int Y_rowStride, ByteBuffer Y_Buffer, int U_rowStride, ByteBuffer U_Buffer, int V_rowStride, ByteBuffer V_Buffer, Surface surface);
  </code></pre>
  
  <p>C++:</p>
  
  <pre><code>const uint8_t NUM_128 = 128;
  const uint8_t NUM_255 = 255;
  
  JNIEXPORT void JNICALL Java_tau_camera2demo_JNIUtils_RGBADisplay(
          JNIEnv *env,
          jobject obj,
          jint srcWidth,
          jint srcHeight,
          jint Y_rowStride,
          jobject Y_Buffer,
          jint U_rowStride,
          jobject U_Buffer,
          jint V_rowStride,
          jobject V_Buffer,
          jobject surface) {
  
  
      uint8_t *srcYPtr = reinterpret_cast&lt;uint8_t *&gt;(env-&gt;GetDirectBufferAddress(Y_Buffer));
      uint8_t *srcUPtr = reinterpret_cast&lt;uint8_t *&gt;(env-&gt;GetDirectBufferAddress(U_Buffer));
      uint8_t *srcVPtr = reinterpret_cast&lt;uint8_t *&gt;(env-&gt;GetDirectBufferAddress(V_Buffer));
  
      ANativeWindow * window = ANativeWindow_fromSurface(env, surface);
      ANativeWindow_acquire(window);
      ANativeWindow_Buffer buffer;
  
      //set output size and format
      //only 3 formats are available:
      //WINDOW_FORMAT_RGBA_8888(DEFAULT), WINDOW_FORMAT_RGBX_8888, WINDOW_FORMAT_RGB_565
      ANativeWindow_setBuffersGeometry(window, 0, 0, WINDOW_FORMAT_RGBA_8888);
      if (int32_t err = ANativeWindow_lock(window, &amp;buffer, NULL)) {
          LOGE("ANativeWindow_lock failed with error code: %d\n", err);
          ANativeWindow_release(window);
      }
  
      //convert YUV_420_888 to RGBA_8888 and display
      uint8_t * outPtr = reinterpret_cast&lt;uint8_t *&gt;(buffer.bits);
      for (size_t y = 0; y &lt; srcHeight; y++)
      {
          uint8_t * Y_rowPtr = srcYPtr + y * Y_rowStride;
          uint8_t * U_rowPtr = srcUPtr + (y &gt;&gt; 1) * U_rowStride;
          uint8_t * V_rowPtr = srcVPtr + (y &gt;&gt; 1) * V_rowStride;
          for (size_t x = 0; x &lt; srcWidth; x++)
          {
              //from Wikipedia article YUV:
              //Integer operation of ITU-R standard for YCbCr(8 bits per channel) to RGB888
              //Y-Y, U-Cb, V-Cr
              //R = Y + V + (V &gt;&gt; 2) + (V &gt;&gt; 3) + (V &gt;&gt; 5);
              //G = Y - ((U &gt;&gt; 2) + (U &gt;&gt; 4) + (U &gt;&gt; 5)) - ((V &gt;&gt; 1) + (V &gt;&gt; 3) + (V &gt;&gt; 4) + (V &gt;&gt; 5));
              //B = Y + U + (U &gt;&gt; 1) + (U &gt;&gt; 2) + (U &gt;&gt; 6);
              uint8_t Y = Y_rowPtr[x];
              uint8_t U = U_rowPtr[(x &gt;&gt; 1)] - NUM_128;
              uint8_t V = V_rowPtr[(x &gt;&gt; 1)] - NUM_128;
              *(outPtr++) = Y + V + (V &gt;&gt; 2) + (V &gt;&gt; 3) + (V &gt;&gt; 5); //R
              *(outPtr++) = Y - ((U &gt;&gt; 2) + (U &gt;&gt; 4) + (U &gt;&gt; 5)) - ((V &gt;&gt; 1) + (V &gt;&gt; 3) + (V &gt;&gt; 4) + (V &gt;&gt; 5)); //G
              *(outPtr++) = Y + U + (U &gt;&gt; 1) + (U &gt;&gt; 2) + (U &gt;&gt; 6); //B
              *(outPtr++) = NUM_255; // gamma for RGBA_8888
          }
      }
  
      ANativeWindow_unlockAndPost(window);
      ANativeWindow_release(window);
  }
  </code></pre>
  
  <p>The whole demo could be found here on Github: <a href="https://github.com/Fung-yuantao/android-camera2demo" rel="nofollow noreferrer">https://github.com/Fung-yuantao/android-camera2demo</a></p>
  
  <p><strong>UPDATE</strong>:</p>
  
  <p>Added the following code after the line calling <code>JNIUtils.RGBADisplay</code>: </p>
  
  <pre><code>        Log.d(TAG, "Y plane pixel stride: " + Y_plane.getPixelStride());
          Log.d(TAG, "U plane pixel stride: " + U_plane.getPixelStride());
          Log.d(TAG, "V plane pixel stride: " + V_plane.getPixelStride());
  </code></pre>
  
  <p>In Logcat:</p>
  
  <pre><code>09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: Y plane pixel stride: 1
  09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: U plane pixel stride: 1
  09-07 06:40:02.576 5376-5392/tau.camera2demo D/Camera2Demo: V plane pixel stride: 1
  </code></pre>
  
  <p>The image format should be planar according to the answer from alijandro.</p>