Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture screen of GLSurfaceView to bitmap

I need to be able to capture an image of a GLSurfaceView at certain moment in time. I have the following code:

relative.setDrawingCacheEnabled(true);
screenshot = Bitmap.createBitmap(relative.getDrawingCache());
relative.setDrawingCacheEnabled(false);
Log.v(TAG, "Screenshot height: " + screenshot.getHeight());
image.setImageBitmap(screenshot); 

The GLSurfaceView is contained within a RelativeLayout, but I have also tries it straight using the GLSurfaceView to try and capture the image. With this I think the screen captures a transparent image, i.e. nothing there. Any help will be appreciated.

like image 851
SamRowley Avatar asked Apr 01 '11 13:04

SamRowley


3 Answers

SurfaceView and GLSurfaceView punch holes in their windows to allow their surfaces to be displayed. In other words, they have transparent areas.

So you cannot capture an image by calling GLSurfaceView.getDrawingCache().

If you want to get an image from GLSurfaceView, you should invoke gl.glReadPixels() in GLSurfaceView.onDrawFrame().

I patched createBitmapFromGLSurface method and call it in onDrawFrame().

(The original code might be from skuld's code.)

private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl)
        throws OutOfMemoryError {
    int bitmapBuffer[] = new int[w * h];
    int bitmapSource[] = new int[w * h];
    IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
    intBuffer.position(0);

    try {
        gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
        int offset1, offset2;
        for (int i = 0; i < h; i++) {
            offset1 = i * w;
            offset2 = (h - i - 1) * w;
            for (int j = 0; j < w; j++) {
                int texturePixel = bitmapBuffer[offset1 + j];
                int blue = (texturePixel >> 16) & 0xff;
                int red = (texturePixel << 16) & 0x00ff0000;
                int pixel = (texturePixel & 0xff00ff00) | red | blue;
                bitmapSource[offset2 + j] = pixel;
            }
        }
    } catch (GLException e) {
        return null;
    }

    return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
}
like image 111
Dalinaum Avatar answered Nov 15 '22 20:11

Dalinaum


Here is a complete solution if you are using a third party library that you just 'pass in' a GLSurfaceView defined in your layout. You won't have a handle on the onDrawFrame() of the renderer, this can be a problem...

To do this you need to queue it up for the GLSurfaceView to handle.

private GLSurfaceView glSurfaceView; // findById() in onCreate
private Bitmap snapshotBitmap;

private interface BitmapReadyCallbacks {
    void onBitmapReady(Bitmap bitmap);
}

/* Usage code
   captureBitmap(new BitmapReadyCallbacks() {

        @Override
        public void onBitmapReady(Bitmap bitmap) {
            someImageView.setImageBitmap(bitmap);
        }
   });
*/

// supporting methods
private void captureBitmap(final BitmapReadyCallbacks bitmapReadyCallbacks) {
    glSurfaceView.queueEvent(new Runnable() {
        @Override
        public void run() {
            EGL10 egl = (EGL10) EGLContext.getEGL();
            GL10 gl = (GL10)egl.eglGetCurrentContext().getGL();
            snapshotBitmap = createBitmapFromGLSurface(0, 0, glSurfaceView.getWidth(), glSurfaceView.getHeight(), gl);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    bitmapReadyCallbacks.onBitmapReady(snapshotBitmap);
                }
            });

        }
    });

}

// from other answer in this question
private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl) {

    int bitmapBuffer[] = new int[w * h];
    int bitmapSource[] = new int[w * h];
    IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
    intBuffer.position(0);

    try {
        gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
        int offset1, offset2;
        for (int i = 0; i < h; i++) {
            offset1 = i * w;
            offset2 = (h - i - 1) * w;
            for (int j = 0; j < w; j++) {
                int texturePixel = bitmapBuffer[offset1 + j];
                int blue = (texturePixel >> 16) & 0xff;
                int red = (texturePixel << 16) & 0x00ff0000;
                int pixel = (texturePixel & 0xff00ff00) | red | blue;
                bitmapSource[offset2 + j] = pixel;
            }
        }
    } catch (GLException e) {
        Log.e(TAG, "createBitmapFromGLSurface: " + e.getMessage(), e);
        return null;
    }

    return Bitmap.createBitmap(bitmapSource, w, h, Config.ARGB_8888);
}
like image 43
aaronvargas Avatar answered Nov 15 '22 19:11

aaronvargas


Note: In this code, when I click the Button, it takes the screenshot as Image and saves it in sdcard location. I used boolean condition and an if statement in onDraw method, because the renderer class may call the onDraw method anytime and anyway, and without the if this code may save lots of images in the memory card.

MainActivity class:

protected boolean printOptionEnable = false;

saveImageButton.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        Log.v("hari", "pan button clicked");
        isSaveClick = true;
        myRenderer.printOptionEnable = isSaveClick;
    }
});

MyRenderer class:

int width_surface , height_surface ;

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    Log.i("JO", "onSurfaceChanged");
    // Adjust the viewport based on geometry changes,
    // such as screen rotation
    GLES20.glViewport(0, 0, width, height);

    float ratio = (float) width / height;

    width_surface =  width ;
    height_surface = height ;
}

@Override
public void onDrawFrame(GL10 gl) {
    try {
        if (printOptionEnable) {
            printOptionEnable = false ;
            Log.i("hari", "printOptionEnable if condition:" + printOptionEnable);
            int w = width_surface ;
            int h = height_surface  ;

            Log.i("hari", "w:"+w+"-----h:"+h);

            int b[]=new int[(int) (w*h)];
            int bt[]=new int[(int) (w*h)];
            IntBuffer buffer=IntBuffer.wrap(b);
            buffer.position(0);
            GLES20.glReadPixels(0, 0, w, h,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE, buffer);
            for(int i=0; i<h; i++)
            {
                //remember, that OpenGL bitmap is incompatible with Android bitmap
                //and so, some correction need.        
                for(int j=0; j<w; j++)
                {
                    int pix=b[i*w+j];
                    int pb=(pix>>16)&0xff;
                    int pr=(pix<<16)&0x00ff0000;
                    int pix1=(pix&0xff00ff00) | pr | pb;
                    bt[(h-i-1)*w+j]=pix1;
                }
            }           
            Bitmap inBitmap = null ;
            if (inBitmap == null || !inBitmap.isMutable()
                || inBitmap.getWidth() != w || inBitmap.getHeight() != h) {
                inBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            }
            //Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            inBitmap.copyPixelsFromBuffer(buffer);
            //return inBitmap ;
            // return Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
            inBitmap = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);

            ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
            inBitmap.compress(CompressFormat.JPEG, 90, bos); 
            byte[] bitmapdata = bos.toByteArray();
            ByteArrayInputStream fis = new ByteArrayInputStream(bitmapdata);

            final Calendar c=Calendar.getInstance();
            long mytimestamp=c.getTimeInMillis();
            String timeStamp=String.valueOf(mytimestamp);
            String myfile="hari"+timeStamp+".jpeg";

            dir_image = new File(Environment.getExternalStorageDirectory()+File.separator+
            "printerscreenshots"+File.separator+"image");
            dir_image.mkdirs();

            try {
                File tmpFile = new File(dir_image,myfile); 
                FileOutputStream fos = new FileOutputStream(tmpFile);

                byte[] buf = new byte[1024];
                int len;
                while ((len = fis.read(buf)) > 0) {
                fos.write(buf, 0, len);
            }
            fis.close();
            fos.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            Log.v("hari", "screenshots:"+dir_image.toString());
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}
like image 4
harikrishnan Avatar answered Nov 15 '22 18:11

harikrishnan