GLSurfaceView截屏成位图

38

我需要在某个时间点捕获一个 GLSurfaceView 的图像。我有以下代码:

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

GLSurfaceView被包含在一个RelativeLayout中,但我也尝试过直接使用GLSurfaceView来尝试捕获图像。使用这种方法似乎会捕获一个透明的图像,即什么都没有。任何帮助将不胜感激。


1
你好,我也遇到了同样的问题,你找到解决方案了吗?如果有,请分享一下,谢谢。 - Dev.Sinto
很抱歉,我没有找到这个问题的答案。 - SamRowley
你在不断地渲染吗? - srinivasan
@SamRowley,你找到解决方案了吗? 如果有的话,请在此链接上告诉我 https://dev59.com/aGoy5IYBdhLWcg3wdt4L - Noman
4个回答

54

SurfaceViewGLSurfaceView 会在它们的窗口中打洞以便显示它们的表面,换句话说,它们有透明区域。

因此,你不能通过调用 GLSurfaceView.getDrawingCache() 来捕获图像。

如果你想从 GLSurfaceView 中获取图像,你应该在 GLSurfaceView.onDrawFrame() 中调用 gl.glReadPixels()

我修改了 createBitmapFromGLSurface 方法并在 onDrawFrame() 中调用它。

(原始代码可能来自于skuld的代码。)

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);
}

1
你可以将 createBitmapFromGLSurface 放在你的 GLSurfaceView 的子类中。在 onDraw() 中调用它非常重要。 - Dalinaum
1
但是在onDraw中,您没有gl的实例。您如何检索它? - Nativ
4
我会尽力为您翻译:在6分钟后,我将自己回答。这个方法应该从渲染器的onDrawFrame()方法中调用,而不是从onDraw()方法中调用。您可以在此处找到实现方式:https://github.com/CyberAgent/android-gpuimage。 - Nativ
1
谢谢!我不知道为什么,但是我删除了所有保存颜色的调用,而是只有int pixel = (texturePixel & 0xffffffff); - 你的组合给出了太多蓝色色调。再次感谢! - TJ Biddle
1
嗨Dalinaum!这个很好用。但是我想知道是否有什么方法可以减少捕获过程引起的延迟。在三星S6上,它需要大约3秒钟,当然表面会冻结。你认为有没有办法缩短这个时间,或者(更好的)异步执行?我很感谢你对此的想法。最好的问候。 - Krzysztof Kansy
显示剩余7条评论

22

如果您正在使用第三方库,只需在布局中定义一个GLSurfaceView并将其“传递”,这里提供了完整的解决方案,您无法掌握渲染器的onDrawFrame()方法,这可能是个问题...

为了解决这个问题,您需要将其排队等待GLSurfaceView进行处理。

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);
}

4

注意: 在这段代码中,当我点击按钮时,它会将屏幕截图保存为图像并保存在sd卡位置。我在onDraw方法中使用了布尔条件和一个if语句,因为渲染器类可能随时以任何方式调用onDraw方法,如果没有if语句,这段代码可能会在内存卡中保存大量图像。

MainActivity类:

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类:

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();
    }
}

0

您可以使用GLTextureView扩展TextureView而不是GlsurfaceView来显示OpenGL数据。

请参见:https://dev59.com/tGct5IYBdhLWcg3wetUo

由于GLTextureView从TextureView扩展而来,因此它具有应该可用的getBitmap函数。

myGlTextureView.getBitmap(int width, int height)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接