Load PNGs from assets using Android NDK

Many developers appear to embed libpng with their NDK project in order to decode PNGs. While libpng does offer great flexibility, the amount of code necessary to decode an image is surprisingly high, and the additional work needed to maintain a libpng build means that most of the time, using the system’s decoding routines is perfectly reasonable.

But wait, isn’t the NDK for C++ development only? True, but usually we are still running in a virtual machine that has access to a large panel of high-level utility libraries. This article actually demonstrates a broader, useful technique I call return-to-JVM that you can use for other purposes than simply PNG loading.

I suggest putting your PNG files in the assets directory of your application, so that they can be accessed by path.

First, let’s decide of a Java class and object that will act as a PNG factory and manager for us. Let’s call it PngManager:

import android.content.res.AssetManager;

public class PngManager
{
    private AssetManager amgr;

    public Bitmap open(String path)
    {
        try
        {
            return BitmapFactory.decodeStream(amgr.open(path));
        }
        catch (Exception e) { }
        return null;
    }

    public int getWidth(Bitmap bmp) { return bmp.getWidth(); }
    public int getHeight(Bitmap bmp) { return bmp.getHeight(); }

    public void getPixels(Bitmap bmp, int[] pixels)
    {
        int w = bmp.getWidth();
        int h = bmp.getHeight();
        bmp.getPixels(pixels, 0, w, 0, 0, w, h);
    }

    public void close(Bitmap bmp)
    {
        bmp.recycle();
    }
}

Now to load the PNG from the C++ part of the program, use the following code:

jobject g_pngmgr;
JNIEnv *g_env;

/* ... */

char const *path = "images/myimage.png";

jclass cls = g_env->GetObjectClass(g_pngmgr);
jmethodID mid;

/* Ask the PNG manager for a bitmap */
mid = g_env->GetMethodID(cls, "open",
                         "(Ljava/lang/String;)Landroid/graphics/Bitmap;");
jstring name = g_env->NewStringUTF(path);
jobject png = g_env->CallObjectMethod(g_pngmgr, mid, name);
g_env->DeleteLocalRef(name);
g_env->NewGlobalRef(png);

/* Get image dimensions */
mid = g_env->GetMethodID(cls, "getWidth", "(Landroid/graphics/Bitmap;)I");
int width = g_env->CallIntMethod(g_pngmgr, mid, png);
mid = g_env->GetMethodID(cls, "getHeight", "(Landroid/graphics/Bitmap;)I");
int height = g_env->CallIntMethod(g_pngmgr, mid, png);

/* Get pixels */
jintArray array = g_env->NewIntArray(width * height);
g_env->NewGlobalRef(array);
mid = g_env->GetMethodID(cls, "getPixels", "(Landroid/graphics/Bitmap;[I)V");
g_env->CallVoidMethod(g_pngmgr, mid, png, array);

jint *pixels = g_env->GetIntArrayElements(array, 0);

Now do anything you want with the pixels, for instance bind them to a texture.

And to release the bitmap when finished:

g_env->ReleaseIntArrayElements(array, pixels, 0);
g_env->DeleteGlobalRef(array);

/* Free image */
mid = g_env->GetMethodID(cls, "close", "(Landroid/graphics/Bitmap;)V");
g_env->CallVoidMethod(g_pngmgr, mid, png);
g_env->DeleteGlobalRef(png);

This will not work out of the box. There are a few last things to do, which will hugely depend on your global application architecture and are thus left as an exercise to the reader:

  • Store an AssetManager object in PngManager::amgr before the first call to open() is made (for instance by calling Activity::getAssets() upon application initialisation).
  • Store in g_env a valid JNIEnv * value (the JNI environment is the first argument to all JNI methods), either by remembering it or by using jvm->AttachCurrentThread().
  • Store in g_pngmgr a valid jobject handle to a PngManager instance (for instance by calling a JNI method with the instance as an argument).
  • Error checking was totally omitted from the code for the sake of clarity.
  • Some of the dynamically retrieved variables could benefit from being cached.

I hope this can prove helpful!

For a C++-only solution to this problem, see Load pngs from assets in NDK by Bill Hsu.

  • Posted: 2011-03-02 04:02 (Updated: 2011-03-04 14:52)
  • Author: sam
  • Categories: android code tip

Comments

1. pablolupo@gmail.com -- 2011-08-12 16:11

Hi Sam, before nothing thank you so much for writing this article and I'm sorry for my english :)

I'm having troubles when I try to call my native function passing my PngManager instance as an argument. The c++ function prototype is this:

JNIEXPORT void JNICALL Java_com_ndkpackage_ndkproject_GameRenderer_nativeInit(JNIEnv* env, jclass cls, jobject* pngManager)

Java declaration: private static native void nativeInit(Object pngLoader);

At Run-time I have this error on LogCat: WARN/dalvikvm(254): No implementation found for native Lcom/ndkpackage/ndkproject/GameRenderer;.nativeInit (Lcom/ndkpackage/ndkproject/PngLoader;)V

Could you help me with this? Thanks in advance. Lupo

4. anonymous -- 2011-08-14 00:19

Fixed. Just send an instance of the PngManager from Java and received it as an jobject pointer as I said before, but I forget to modify the c++ native function prototype... Sorry.

Anyway, I still having problems at this line:

jobject png = g_env->CallObjectMethod(g_pngmgr, mid, name);

png always is null. Any sugestion? Thanks. Lupo

5. sam -- 2011-08-14 10:09

@Lupo: if png is null and there was no crash, it means the open() method was properly called, but it failed for some reason. You will have to hook some error reporting in open(), right here:

           return BitmapFactory.decodeStream(amgr.open(path));
        }
        catch (Exception e) { /* error reporting goes here */ }
        return null;
    }
6. anonymous -- 2012-06-11 10:40

I am wondering if it is possible to load the bitmap as a texture into openGL in the java and have it then available to use in NDK openGL?

7. moggiozzi -- 2012-08-21 20:53

Hi Sam. I try do it with native-activity project in ndk samples. I add package myapp.mypack and add to package PngManager class. But when i try get jclass for PngManager in android_main function i get NULL. My code:

void android_main(struct android_app* state) {
	env = state->activity->env;
	jvm = state->activity->vm;
	jvm->AttachCurrentThread(&env, NULL);
	jclass fileClass = env->FindClass("myapp/mypack/PngManager");
	if(fileClass==NULL)
		LOGI("fileClass is NULL");

If I'm trying to, for example, "java/io/File", I get non-null pointer. It would be nice if you suggested solution.

8. ZeDuS -- 2013-09-02 20:11

"I am wondering if it is possible to load the bitmap as a texture into openGL in the java and have it then available to use in NDK openGL? "

Of course you can, since textures in GLES (1/2) are basically just IDs, you can pass them as int back to native code. I've managed to do it with a "special" texture (camera preview) and I'm sure it's possible with "simple" textures.

9. titmouse001@gmail.com -- 2015-05-14 00:21

Thanks - works like a dream. One small thing... Don't forget to clean up that cls instance as a last call, it's possible to run out of refs after about 512 calls. env->DeleteLocalRef( cls);

This link talks about how to make it global rather than calling the above delete each time... http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/refs.html

10. anonymous -- 2016-09-13 08:25

Hi,

Thank you for the awesome tutorial. I am having problems with applying this method to multiple bitmap files though. I keep getting dvmHeapBitmapScanWalk(HeapBitmap*, void (*)(Object*, void*, void*), void*) error.

I wonder if you can provide an example of loading multiple files.

Cheers

185. BernardBax -- 2016-10-20 21:55

Essay on best teacher http://www.thegreatnewsinc.com/Critical-Thinking-Paper - Critical Thinking Paper, Buy research papers Best essay review services http://youngwillstone.com/edit-college-essays-online - Edit college essays online, Academic report writing Essay writing courses http://verybestphotos.com/statics-homework-help - Statics homework help, College paper writing Need help for doing my assignment http://www.thegreatnewsinc.com/write-my-paper-write-my-paper-for-$10-or-less - Write my paper write my paper for $10 or less, Writing lab reports Easiest way to write an essay http://verybestphotos.com/need-help-writing-a-essay - Need help writing a essay, English homework help

271. subanal -- 2016-11-27 00:43

raK4GL There is visibly a bunch to realize about this. I believe you made certain nice points in features also.

368. useful source -- 2017-01-31 18:23

ER8FcF Thanks a lot for the article. Keep writing.

369. that site -- 2017-02-01 00:33

8uEuaG written. In my opinion, it might bring your website a little bit more interesting.

Add New Comment