February 21, 2012
Writing a basic image filter in Android using NDK

I needed to implement some image filters on bitmaps for my Android project. I first attempted the filters in pure Java but it turns out to be too slow and consume too much memory.

Bitmap handling in Android has always been a pain point and there is too much memory overhead.

To get access to the underlying pixel data from a Bitmap in Java you use Bitmap.getPixels() which returns an integer array of Color objects. There is just too much object creation there meaning too much overhead.

So the solution is to go native C.

For a proof of concept I wanted to try implementing a filter in native C using the Android Native Development Kit (NDK). My test case adjusts a bitmaps level of brightness. 

The complete source is available on GitHub: https://github.com/ruckus/android-image-filter-ndk

First off you will need to download the Android NDK and place the directory somewhere handy, I put mine at /AndroidSDK/android-ndk-r7b along side my standard Android SDK.

In your Activity you will need to declare a method using the native keyword, this tells Android that the implementation for this method is in a C library, which we will generate shortly.

In the jni folder you will need any C files, I just have a single file, and a Makefile template.

Compile the library using the ndk-build command in the NDK:

If all goes well then it should compile cleanly and leave you with a libimageprocessing.so shared object.

Crack open jni/imageprocessing.c and lets have a look. Notice that the method name we call is

Java_com_example_ImageActivity_brightness

Which is the complete Java package name plus the method that we annotated with the native keyword in the Activity. The first argument to this method is the JNI environment which Android supplies for us. Any additional arguments are the user supplied ones part of the signature in Java. Looking at the signature in Java:

public native void brightness(Bitmap bmp, float brightness);

We pass in the Bitmap and the brightness value.

Looking back at the Java Activity notice that C receives the Bitmap and writes back the modified pixels to the same object.

The Android NDK gives us some basic bitmap functions that we need to use to get at the actual pixel data:

AndroidBitmap_lockPixels

Its important than any calls to AndroidBitmap_lockPixels are complemented with a AndroidBitmap_unlockPixels when you are done.

In the core filter method brightness the logic is the following:

1) Iterate over each row of pixel data.

2) After we grab the whole row we iterate over each column.

3) The pixel data is a packed integer which contains the actual RGB values

4) Since we need to manipulate each RGB value we extract the components out

5) We multiplty by the brightness factor and then constrain it to a value between 0 and 255

6) Then finally we write the modified pixels back into the structure.

Some before and after screenshots:

Before

After