BLOG ARTICLE BitmapFactory Texture Opengles | 1 ARTICLE FOUND

  1. 2014.09.22 Android, NDK OpenGLES2.0 Texture

1. Load texture

  I used Android BitmapFactory to decode PNG texture. If you want one source multi flatform, Use libpng. This writing show example BitmapFactory decoding. First we have to load image. Theretofore we have to decide resource foloder.


  A. res/raw

    Many developer use assets foloder to manage resource, but I decide resource folder res/raw because res folder managed to R.java, there could be used on Android Java easily. 





  B. Load resource

    First to manage loaded resource, I made Texture class

public class Texture 
{
	public String		mName;
	public ByteBuffer	mEncodeData;
}

    It have texture name and ByteBuffer that is used in decoding functions. 

    And save  loaded resource to Texture object. 

public static boolean loadImage(String strName)
{
	Log.i("AndroidOpenGL", "loadImage start, name : "+ strName);
	if(MainActivity.context == null)
	{
		Log.w("AndroidOpenGL", "Failed get Bitmap from Asset. context is null");
		return false;
	}
	if(strName == null)
		return false;
	
    try 
    {
    	Texture tex 	= new Texture();
	Resources res 	= MainActivity.context.getResources();
	int resId 	= res.getIdentifier("raw/"+strName, null, MainActivity.context.getPackageName());
	    
	if(resId == 0)
	{
		Log.w("AndroidOpenGL", "Can't find resource : "+ strName);
	    	return false;
	}
	    
	BufferedInputStream  is 	= new BufferedInputStream(res.openRawResource(resId));
	ReadableByteChannel channel	= Channels.newChannel(is);

    	tex.mName	= strName;
    	int fileSize	= (int) res.openRawResourceFd(res.getIdentifier("raw/"+strName, null, MainActivity.context.getPackageName())).getLength();
    	tex.mEncodeData = ByteBuffer.allocate(fileSize);
    	
    	int r = 0;
    	while (tex.mEncodeData.remaining() > 0 && r != -1) 
    		r = channel.read(tex.mEncodeData);        
    	ResourceManager.instance.addTexture(tex);
    	
    	
    	if(is != null)
    		is.close();
    	
    	return true;
    } catch (IOException e) 
    {
	Log.w("AndroidOpenGL", e.toString());
	e.printStackTrace();
    }
    return false;
}

    I used ReadableByteChannel to get file size because I want to allocate memory bytes to Texture object.


2. Decode Texture

  If you use libpng library, It could be used on other flatform like IOS. But libpng code little hard to use and tiresome. So I used Android API.

public static void decodeImage(String strName, boolean add_to_atlas) { Log.i("AndroidOpenGL", "decodeImage start, name : "+ strName); if(MainActivity.context == null) { Log.w("AndroidOpenGL", "Failed get Bitmap from Asset. context is null"); return ; } if(strName == null) return; Texture tex = ResourceManager.instance.getTexture(strName); if(tex == null) return; Bitmap bitmap = null; int width; int height; ByteBuffer bb = null; try { bitmap = BitmapFactory.decodeByteArray(tex.mEncodeData.array(), 0, tex.mEncodeData.capacity()); width = bitmap.getWidth(); height = bitmap.getHeight(); bb = ByteBuffer.allocate(width*height*4); bitmap.copyPixelsToBuffer(bb); bitmap.recycle(); }catch(Exception e) { Log.w("AndroidOpenGL", "OpenGL : Get Bitmap Failed"); Log.w("AndroidOpenGL", e.toString()); return ; } Log.i("AndroidOpenGL", "Allocated direct-buffer capacity:"+ bb.capacity()); Log.i("AndroidOpenGL", "decodeImage end, bb width:"+ width +" height:"+height); onDecodeImageComplete(bb.array(), width, height, strName, add_to_atlas); }

  You can get byte array using BitmapFactory. [onDecodeImageComplete] is jni function that send decoded resource to c++. 

  Caution thing is your resource's width and height have to be 2^n(exponent) because OpenGL glTexImage2D function only get 2^n width and height to parameter. 

  So I used image that size is 32*32, 64*128 etc.



3. Jni 

  After decoding, we have to send data to c++. Below show [onDecodeImageComplete] code(Ignore add_to_atlas parameter, It don't need to example)

JNIEXPORT void JNICALL Java_com_snj_opengles_MainNDK_onDecodeImageComplete(JNIEnv * a, jclass b, jbyteArray pixels, jint width, jint height, jstring name, jboolean add_to_atlas) { Log::info_print("onDecodeImageComplete start"); //Use direct buffer // GLubyte* data =(GLubyte*)a->GetDirectBufferAddress(pixels); //Use non-direct buffer int len = width*height*4; GLbyte* data = new GLbyte[len]; a->GetByteArrayRegion(pixels,0, len, data); len = a->GetStringUTFLength(name); char* texName = new char[len+1]; a->GetStringUTFRegion(name,0,len,texName); shared_ptr<Texture> tex = make_shared<Texture>(); tex->m_name = string(texName); tex->m_data = (GLubyte*)data; tex->m_width = width; tex->m_height = height; tex->m_tex_map_width = width; tex->m_tex_map_height = height; tex->m_tex_map_ratio_width = tex->m_width/(float)tex->m_tex_map_width; tex->m_tex_map_ratio_height = tex->m_height/(float)tex->m_tex_map_height;

tex->m_request_add_to_atlas = add_to_atlas; ResourceManager::getInstance()->addToTextureCompletList(tex); delete[] (texName); Log::info("onDecodeImageComplete end"); }

  Don't use Direct buffer. When I use direct buffer, I knew that allocating direct buffer is very slow. So I use non-direct buffer.     

  Use GetByteArrayRegion, It is optimized function to copy data. 

  When allocating char array, size is GetStringUTFLength +1. If you don't +1, it would be crashed when you delete array.


4. result

 It is finish decode texture, If you use opengles 2.0 and want to know  shader, reference http://shakddoo.tistory.com/9



<OpenGL Dirary>