androidbitmapandroid-ndkjava-native-interfaceimage-rotation

Rotating a bitmap using JNI & NDK


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:


better solution

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


Solution

  • 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.