Recently working on a 3D version of the Eva Desk Clock, and targeting to Android 2.2+ with Open GL ES 2.0 support.

I have almost all the assets in 2D and it would be easy to move it to 3D…. but need to load the texture to Open GL. It is very strange that it looks like ES20 doesn’t seems too official supported in Android, but anyway, the static class GLES20 have all the function that I can access, but without any utility or helper libraries, that means I have to build everything my own, including perspective, camera, transformation etc. One problem I encountered, is loading the texture (with alpha channel) into the GL engine.

What I have is based on the book OpenGL ES 2.0 Programming Guide (which is a nice book with great example codes in many platforms including Android, IOS etc) and http://tkcodesharing.blogspot.com/2008/05/working-with-textures-in-androids.html.

Both of their method is pretty similar, loading the pixels one by one and then reorder it to the byte order accepted by Open GL. Here’s how:

        for ( int y = 0; y < bitmap.getHeight(); y++ )
            for ( int x = 0; x < bitmap.getWidth(); x++ )
            {
                int pixel = bitmap.getPixel(x, y);
                int alpha = (pixel >> 24) & 0xFF;
                int red = (pixel >> 16) & 0xFF;
                int green = (pixel >> 8 ) & 0xFF;
                int blue = (pixel) & 0xFF;
                ib.put(red<<24 | green << 16 | blue << 8 | alpha);
            }

Since the pixel in Android, is defined in ARGB (32-bit, each channel is 8 byte), but for Open GL, the pixel is RGBA, the above conversion read pixels from the bitmap one by one, and then put into the IntBuffer (named ib).

It turns out, yes, it works, but very slow. Then I dig into the Method profiling (part of SDK tools) and found out that in the above codes, more than half of the time is used on ‘bitmap.getPixel()’, so, it is simple, I just need to move that part out by using it’s variant ‘getPixels’:

    int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for(int i=0; i<pixels.length; i++){
            int pixel = pixels[i];
            int alpha = (pixel >> 24) & 0xFF;
            int red = (pixel >> 16) & 0xFF;
            int green = (pixel >> 8 ) & 0xFF;
            int blue = (pixel) & 0xFF;
            ib.put(red<<24 | green << 16 | blue << 8 | alpha);
        }

This works much faster (more than twice the performance)!  As you can see in the above example, a number of temp variable and bit mask operations is done, which can be further simplified as:

        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for(int i=0; i<pixels.length; i++){
            ib.put(pixels[i] << 8 | pixels[i] >>> 24);
        }

So, following is my final version of loading texture to GL ES 2.0 in Android:

    private static int loadTexture(InputStream is)
    {
        int[] textureId = new int[1];
        Bitmap bitmap;
        bitmap = BitmapFactory.decodeStream(is);

        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bitmap.getWidth() * bitmap.getHeight() * 4);
        byteBuffer.order(ByteOrder.BIG_ENDIAN);
        IntBuffer ib = byteBuffer.asIntBuffer();

        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for(int i=0; i<pixels.length; i++){
            ib.put(pixels[i] << 8 | pixels[i] >>> 24);
        }

        bitmap.recycle();

        byteBuffer.position(0);
        GLES20.glGenTextures ( 1, textureId, 0 );
        GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, textureId[0] );

        GLES20.glTexImage2D ( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(), 0,
                              GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuffer );

        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );

        return textureId[0];
    }
Advertisements