Rotating a bitmap using JNI & NDK

17,556

Since you're using ARGB_8888 format every pixel is an uint32_t not uint16_t. Try changing your rotated Bitmap creation to use uint32_t for source and destination arrays and it should work better.

Share:
17,556
android developer
Author by

android developer

Really like to develop Android apps & libraries on my spare time. Github website: https://github.com/AndroidDeveloperLB/ My spare time apps: https://play.google.com/store/apps/developer?id=AndroidDeveloperLB

Updated on June 04, 2022

Comments

  • android developer
    android developer about 2 years

    Background:

    I've decided that since bitmaps take a lot of memory which can cause out-of-memory errors easily, I will put the hard, memory consuming work on C/C++ code .

    The steps I use for rotating a bitmap are:

    1. read bitmap info (width,height)
    2. store bitmap pixels into an array.
    3. recycle the bitmap.
    4. create a new bitmap of opposite size.
    5. put the pixels into the new bitmap.
    6. free the pixels and return the bitmap.

    The problem:

    Even though everything seems to run without any errors, the output image is not a rotation of the original. In fact, it ruins it completely.

    The rotation should be counter clock wise, 90 degrees.

    Example (screenshot is zoomed in) of what I get:

    enter image description here

    So as you can see, not only the colors became weirder, but the size doesn't match what I've set to it. Something is really weird here.

    Maybe I don't read/put the data correctly?

    Of course this is just an example. The code should work fine on any bitmap, as long as the device has enough memory to hold it. Also, I might want to do other operations on the bitmap other than rotating it.

    Code I've created :

    Android.mk file:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := JniTest
    LOCAL_SRC_FILES := JniTest.cpp
    LOCAL_LDLIBS := -llog
    LOCAL_LDFLAGS += -ljnigraphics
    include $(BUILD_SHARED_LIBRARY)
    APP_OPTIM := debug
    LOCAL_CFLAGS := -g
    

    cpp file:

    #include <jni.h>
    #include <jni.h>
    #include <android/log.h>
    #include <stdio.h>
    #include <android/bitmap.h>
    #include <cstring>
    #include <unistd.h>
    
    #define  LOG_TAG    "DEBUG"
    #define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
    #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    
    extern "C"
      {
      JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap);
      }
    
    JNIEXPORT jobject JNICALL Java_com_example_jnitest_MainActivity_rotateBitmapCcw90(JNIEnv * env, jobject obj, jobject bitmap)
      {
      //
      //getting bitmap info:
      //
      LOGD("reading bitmap info...");
      AndroidBitmapInfo info;
      int ret;
      if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0)
        {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return NULL;
        }
      LOGD("width:%d height:%d stride:%d", info.width, info.height, info.stride);
      if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
        {
        LOGE("Bitmap format is not RGBA_8888!");
        return NULL;
        }
      //
      //read pixels of bitmap into native memory :
      //
      LOGD("reading bitmap pixels...");
      void* bitmapPixels;
      if ((ret = AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels)) < 0)
        {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return NULL;
        }
      uint32_t* src = (uint32_t*) bitmapPixels;
      uint32_t* tempPixels = new uint32_t[info.height * info.width];
      int stride = info.stride;
      int pixelsCount = info.height * info.width;
      memcpy(tempPixels, src, sizeof(uint32_t) * pixelsCount);
      AndroidBitmap_unlockPixels(env, bitmap);
      //
      //recycle bitmap - using bitmap.recycle()
      //
      LOGD("recycling bitmap...");
      jclass bitmapCls = env->GetObjectClass(bitmap);
      jmethodID recycleFunction = env->GetMethodID(bitmapCls, "recycle", "()V");
      if (recycleFunction == 0)
        {
        LOGE("error recycling!");
        return NULL;
        }
      env->CallVoidMethod(bitmap, recycleFunction);
      //
      //creating a new bitmap to put the pixels into it - using Bitmap Bitmap.createBitmap (int width, int height, Bitmap.Config config) :
      //
      LOGD("creating new bitmap...");
      jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
      jstring configName = env->NewStringUTF("ARGB_8888");
      jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
      jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(bitmapConfigClass, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
      jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass, valueOfBitmapConfigFunction, configName);
      jobject newBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapFunction, info.height, info.width, bitmapConfig);
      //
      // putting the pixels into the new bitmap:
      //
      if ((ret = AndroidBitmap_lockPixels(env, newBitmap, &bitmapPixels)) < 0)
        {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return NULL;
        }
      uint32_t* newBitmapPixels = (uint32_t*) bitmapPixels;
      int whereToPut = 0;    
      for (int x = info.width - 1; x >= 0; --x)
        for (int y = 0; y < info.height; ++y)
          {
          uint32_t pixel = tempPixels[info.width * y + x];
          newBitmapPixels[whereToPut++] = pixel;
          }
      AndroidBitmap_unlockPixels(env, newBitmap);
      //
      // freeing the native memory used to store the pixels
      //
      delete[] tempPixels;
      return newBitmap;
      }
    

    java file:

      static
        {
        System.loadLibrary("JniTest");
        }
    
      /**
       * rotates a bitmap by 90 degrees counter-clockwise . <br/>
       * notes:<br/>
       * -the input bitmap will be recycled and shouldn't be used anymore <br/>
       * -returns the rotated bitmap . <br/>
       * -could take some time , so do the operation in a new thread
       */
      public native Bitmap rotateBitmapCcw90(Bitmap bitmap);
    
    ...
      Bitmap rotatedImage=rotateBitmapCcw90(bitmapToRotate);
    

    EDIT: after I got my answer, I wish to share this code and notes about it to everyone:

    • in order for it to work, i've replaced in the code every instance of "uint16_t" with "uint32_t" (that's the bug on my code I've asked about).

    • input and output bitmap must be with 8888 config (which is ARGB )

    • input bitmap will be recycled during the process.

    • the code rotates the image 90 degrees counter clock wise. Of course you can change it depending on your needs.


    better solution

    i've made a nice post having this functionality and others, here .

  • android developer
    android developer over 11 years
    thank you . it worked great . i can't believe i had so much work and i didn't do this thing right .
  • android developer
    android developer over 11 years
    do you have any nice speed optimizations tips for this code ?