The jnigraphics
library provides a C-based interface for native code to access the pixel buffers of Java bitmap objects, which is available as a stable native API on Android 2.2 system images and higher. This recipe discusses how to use the jnigraphics
library.
Readers are expected to know how to create an Android NDK project. We can refer to the Writing a Hello NDK program recipe of Chapter 1, Hello NDK for detailed instructions.
The following steps describe how to create a simple Android application which demonstrates the usage of the jnigraphics
library:
JNIGraphics
. Set the package name as cookbook.chapter7.JNIGraphics
. Refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface for more detailed instructions.MainActivity.java
and RenderView.java
in the cookbook.chapter7.JNIGraphics
package. The RenderView.java
loads the JNIGraphics
native library, calls the native naDemoJniGraphics
method to process a bitmap, and finally display the bitmap. The MainActivity.java
files creates a bitmap, passes it to the RenderView
class, and sets the RenderView
class as its content view.mylog.h
and JNIGraphics.cpp
files under the jni
folder. The mylog.h
contains the Android native logcat
utility functions, while the JNIGraphics.cpp
file contains the native code to process the bitmap with the jnigraphics
library functions. A part of the code in the JNIGraphics.cpp
file is shown as follows:void naDemoJniGraphics(JNIEnv* pEnv, jclass clazz, jobject pBitmap) { int lRet, i, j; AndroidBitmapInfo lInfo; void* lBitmap; //1. retrieve information about the bitmap if ((lRet = AndroidBitmap_getInfo(pEnv, pBitmap, &lInfo)) < 0) { return; } if (lInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { return; } //2. lock the pixel buffer and retrieve a pointer to it if ((lRet = AndroidBitmap_lockPixels(pEnv, pBitmap, &lBitmap)) < 0) { LOGE(1, "AndroidBitmap_lockPixels() failed! error = %d", lRet); } //3. manipulate the pixel buffer unsigned char *pixelBuf = (unsigned char*)lBitmap; for (i = 0; i < lInfo.height; ++i) { for (j = 0; j < lInfo.width; ++j) { unsigned char *pixelP = pixelBuf + i*lInfo.stride + j*4; *pixelP = (unsigned char)0x00; //remove R component // *(pixelP+1) = (unsigned char)0x00; //remove G component // *(pixelP+2) = (unsigned char)0x00; //remove B component // LOGI(1, "%d:%d:%d:%d", *pixelP, *(pixelP+1), *(pixelP+2), *(pixelP+3));} } //4. unlock the bitmap AndroidBitmap_unlockPixels(pEnv, pBitmap); }
Android.mk
file in the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JNIGraphics LOCAL_SRC_FILES := JNIGraphics.cpp LOCAL_LDLIBS := -llog -ljnigraphics include $(BUILD_SHARED_LIBRARY)
In our sample project, we modified the bitmap passed to the native naDemoJniGraphics
function by setting one of its RGB components to zero.
The following steps should be followed to use the
jnigraphics
library:
<android/bitmap.h>
header in the source code where we use the jnigraphics
API.jnigraphics
library by including the following line in the Android.mk
file.LOCAL_LDLIBS += -ljnigraphics
AndroidBitmap_getInfo
to retrieve the information about a bitmap object. The AndroidBitmap_getInfo
function has the following prototype:int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap, AndroidBitmapInfo* info);
The function accepts a pointer to the JNIEnv
structure, a reference to the bitmap object, and a pointer to the AndroidBitmapInfo
structure. If the call is successful, the data structure pointed by info
will be filled.
The AndroidBitmapInfo
is defined as follows:
typedef struct { uint32_t width; uint32_t height; uint32_t stride; int32_t format; uint32_t flags; } AndroidBitmapInfo;
width
and height
indicate the pixel width and height of the bitmap. stride
refers to the number of bytes to skip between rows of the pixel buffer. The number must be no less than the width in bytes. In most cases, stride
is the same as width
. However, sometimes pixel buffer contains paddings so stride can be bigger than bitmap width
.
The format
is the color format, which can be ANDROID_BITMAP_FORMAT_RGBA_8888
, ANDROID_BITMAP_FORMAT_RGB_565
, ANDROID_BITMAP_FORMAT_RGBA_4444
, ANDROID_BITMAP_FORMAT_A_8
, or ANDROID_BITMAP_FORMAT_NONE
as defined in the bitmap.h
header file.
In our example, we used ANDROID_BITMAP_FORMAT_RGBA_8888
as the bitmap format. Therefore, every pixel takes 4 bytes.
AndroidBitmap_lockPixels
function:int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
If the call succeeds, the *addrPtr
pointer will point to the pixels of the bitmap. Once the pixel address is locked, the memory for the pixels will not move until the pixel address is unlocked.
AndroidBitmap_unlockPixels
:int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
Note that this function must be called if the AndroidBitmap_lockPixels
function succeeds.
Recall that we used the jnigraphics
library to load textures in the Mapping texture to 3D objects with OpenGL ES 1.x API recipe in Chapter 4, Android NDK OpenGL ES API. We can revisit the recipe for another example of how we use the jnigraphics
library.