Android: How to change OpenGLES texture data / bitmap from gallery / camera

4.7k views Asked by At

I try reuse Android Media Effects samples and want to be able to change texture data with new bitmap from camera or gallery pick. No solution so far

This is the initial load texture from the sample : https://github.com/googlesamples/android-MediaEffects/blob/master/Application/src/main/java/com/example/android/mediaeffects/MediaEffectsFragment.java

    private void loadTextures() {
        // Generate textures
        GLES20.glGenTextures(2, mTextures, 0);

        // Load input bitmap
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.puppy);
        mImageWidth = bitmap.getWidth();
        mImageHeight = bitmap.getHeight();
        mTexRenderer.updateTextureSize(mImageWidth, mImageHeight);

        // Upload to texture
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        // Set texture parameters
        GLToolbox.initTexParams();
    }

and this is my trouble function :

public void changeBackground(Uri bitmapUri) {

  Log.d(TAG, "changeBackground : " + bitmapUri.getPath());

  Bitmap bitmap = BitmapFactory.decodeFile(bitmapUri.getPath());
  mImageWidth = bitmap.getWidth();
  mImageHeight = bitmap.getHeight();
  mTexRenderer.updateTextureSize(mImageWidth, mImageHeight);

  GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, bitmap);

  theBackground.requestRender(); //my GLSurfaceView
  bitmap.recycle();
}

I tried several ways :

  • using texSubImage2D only -> texture not updated

  • delete and recreate textures with new bitmap -> black texture

note: the bitmap dimension is fixed, at 1080 x 1080

How to change/modify/replace texture data with new bitmap ?

UPDATE:

I think the problem more relate to this question : Setting texture on 3D object using image from photo gallery. i call changeBackground() after grabbing the image from gallery intent and then crop it (another intent).

Now, how to effectively change texture data with bitmap from gallery intent? anyone have a clue?

SOLVED

I solved the problem by defering texture load process to onSurfaceChange() function. Simply use a flag to indicate the file to load and onSurfaceChange check the flag again.

private Uri mFileToLoad = null;

public void changeBackground(Uri bitmapUri) {
   Log.d(TAG, "changeBackground : " + bitmapUri.getPath());
   mFileToLoad = bitmapUri;
   theBackground.requestRender();
}

and my onSurfaceChange()

    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.d(TAG, "onSurfaceChanged w:" + width + " h:" + height);
        if (mFileToLoad!=null) {
            GLES20.glDeleteTextures(mTextures.length, mTextures, 0);
            GLES20.glGenTextures(mTextures.length, mTextures, 0);

            Bitmap bitmap = BitmapFactory.decodeFile(mFileToLoad.getPath());
            mImageWidth = bitmap.getWidth();
            mImageHeight = bitmap.getHeight();
            mTexRenderer.updateTextureSize(mImageWidth, mImageHeight);

            // Upload to texture
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

            // Set texture parameters
            GLToolbox.initTexParams();

            mFileToLoad = null;
            bitmap.recycle();
        }

        if (mTexRenderer != null) {
            mTexRenderer.updateViewSize(width, height);
        }
    }

So far it works.

1

There are 1 answers

2
Reto Koradi On BEST ANSWER

It looks like your changeBackground() method is probably called from the UI thread, or at least a thread other than the rendering thread.

GLSurfaceView creates a separate rendering thread, which has a current OpenGL context when your rendering methods are called. It is therefore ready for your OpenGL calls. This is not the case for all other threads. The current OpenGL context is per thread. So unless you do something about it (which is possible, but adds significant complexity), you can't make OpenGL calls from other threads.

There are a few options to resolve this. The easiest one is probably using the queueEvent() method on the GLSurfaceView, which allows you to pass in a Runnable that will be executed in the rendering thread.

I'm not sure what the mTexRenderer.updateTextureSize() method in your code does. But generally, if the texture size changes, you'll have to use GLUtils.texImage2D() instead of GLUtils.texSubImage2D() to update the texture.

The code could look like this (untested, so it might not work exactly as typed):

final Bitmap bitmap = BitmapFactory.decodeFile(bitmapUri.getPath());

theBackground.queueEvent(new Runnable() {
    @Override
    void run() {
        mImageWidth = bitmap.getWidth();
        mImageHeight = bitmap.getHeight();
        mTexRenderer.updateTextureSize(mImageWidth, mImageHeight);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        bitmap.recycle();
    }
});

theBackground.requestRender();