12

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 .

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Since you're using ``ARGB_8888`` format should you be using ``uint32_t`` for creating rotated Bitmap? – harism Jan 18 '13 at 12:23
  • What makes you think it'll take less memory to hold a bitmap in C than it does in Java? – Ridcully Jan 18 '13 at 12:34
  • 1
    harism , changed it and it worked !!! thank you . please put an answer so that i could tick it . @Ridcully : it doesn't . it removes the limitation of max heap size , which might be too low on some cases .for example , an 8MP camera image could take 30MB or RAM , and using the normal rotation technique will make it use double this amount of memory . – android developer Jan 18 '13 at 12:42
  • @harism , do you know by any chance how i could also use other bitmap configs , and if i will need to use uint16_t for them ? – android developer Jan 18 '13 at 12:44
  • @androiddeveloper unfortunately I'm not exactly sure how e.g RGB_565 is packed into memory. I would guess they pack them as ``uint16_t`` to save memory but it's best to verify this first. – harism Jan 18 '13 at 12:50
  • There are much easier, more efficient ways of rotating a bitmap in android. Try using a matrix post rotate – Jameo May 06 '13 at 16:26
  • @Jameo if you do it, you will use 2 bitmaps and it can cause OOM , right? that's the whole point of why I made this code. using this code, you use only the needed size of the bitmap, plus you have full control of what to put into it. – android developer May 06 '13 at 18:34
  • @androiddeveloper : Please enlighten me how to achieve like these : http://stackoverflow.com/questions/26404564/how-to-give-fluid-effect-resize-bitmap-wrt-touch-events-using-opencv4android. – Vikalp Patel Oct 27 '14 at 13:04
  • @VikalpPatel Sorry, I'm not an expert in opencv4android, and as I remember, it's quite a large library with tons of features. However, as I've read your question, this looks like something that's not related to bitmaps... I'm pretty sure there is a solution for this already. do you want me to check it out? – android developer Oct 27 '14 at 15:26
  • @androiddeveloper : I would be much grateful if you check out how to achieve something like Virtual Make Over application do these. As I'm much far away Image Processing keeps baffling as zero output comes EOD. :/ – Vikalp Patel Oct 27 '14 at 18:18

1 Answers1

6

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.

harism
  • 6,011
  • 1
  • 36
  • 31