Android截图表面视图显示黑屏

时间:2015-01-07 10:41:06

标签: android surfaceview

我试图通过代码获取我的游戏截图并通过意图分享。我可以做这些事情,但截图总是显示为黑色。以下是与共享屏幕截图相关的代码:

View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap

这是在MainActivity中:

view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.FILL_PARENT,
            RelativeLayout.LayoutParams.FILL_PARENT));

public static SurfaceView getView() {
    return view;
}

观点本身:

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc

这就是我绘制一切的方式:

Canvas canvas = surfaceHolder.lockCanvas(null);
        if (canvas != null) {
                Game.draw(canvas);
...

好的,根据一些答案,我构建了这个:

public static void share() {
    Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
    Calendar c = Calendar.getInstance();
    Date d = c.getTime();
    String path = Images.Media.insertImage(
            Game.context.getContentResolver(), screen, "screenShotBJ" + d
                    + ".png", null);
    System.out.println(path + " PATH");
    Uri screenshotUri = Uri.parse(path);
    final Intent emailIntent = new Intent(
            android.content.Intent.ACTION_SEND);
    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
    emailIntent.setType("image/png");
    Game.context.startActivity(Intent.createChooser(emailIntent,
            "Share High Score:"));
}

Gameview包含以下方法:

public static Bitmap SavePixels(int x, int y, int w, int h) {
    EGL10 egl = (EGL10) EGLContext.getEGL();
    GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
    int b[] = new int[w * (y + h)];
    int bt[] = new int[w * h];
    IntBuffer ib = IntBuffer.wrap(b);
    ib.position(0);
    gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
    for (int i = 0, k = 0; i < h; i++, k++) {
        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 - k - 1) * w + j] = pix1;
        }
    }

    Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
    return sb;
}

屏幕截图仍为黑色。我保存它的方式可能有问题吗?

我尝试了几种不同的方法来获取屏幕截图,但它们都没有工作: 上面代码中显示的是最常建议的一个。但它似乎没有用。 这是使用SurfaceView的问题吗?如果是这样,为什么view.getDrawingCache(true)甚至存在,如果我不能使用它,我该如何解决这个问题?

我的代码:

public static void share() {
    // GIVES BLACK SCREENSHOT:
    Calendar c = Calendar.getInstance();
    Date d = c.getTime();

    Game.update();
    Bitmap.Config conf = Bitmap.Config.RGB_565;
    Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
    Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
    canvas.setBitmap(image);
    Paint backgroundPaint = new Paint();
    backgroundPaint.setARGB(255, 40, 40, 40);
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
            backgroundPaint);
    Game.draw(canvas);
    Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
            Screen.height);
    canvas.setBitmap(null);
    GameThread.surfaceHolder.unlockCanvasAndPost(canvas);

    String path = Images.Media.insertImage(
            Game.context.getContentResolver(), screen, "screenShotBJ" + d
                    + ".png", null);
    System.out.println(path + " PATH");
    Uri screenshotUri = Uri.parse(path);
    final Intent emailIntent = new Intent(
            android.content.Intent.ACTION_SEND);
    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
    emailIntent.setType("image/png");
    Game.context.startActivity(Intent.createChooser(emailIntent,
            "Share High Score:"));
}

谢谢。

5 个答案:

答案 0 :(得分:53)

对此有很多疑惑,还有一些correct answers

这是这笔交易:

  1. SurfaceView有两个部分,Surface和View。 Surface与所有View UI元素位于完全独立的层上。 getDrawingCache()方法仅适用于View图层,因此它不会捕获Surface上的任何内容。

  2. 缓冲区队列有一个生产者 - 消费者API,它只能有一个生产者。 Canvas是一个制作人,GLES是另一个制作人。您无法使用Canvas进行绘制并使用GLES读取像素。 (从技术上讲,如果Canvas正在使用GLES并且当您去读取像素时正确的EGL上下文是正确的,那么可以,但这并不能保证。不会加速对Surface的渲染渲染在任何已发布的Android版本中,所以现在没有希望它能够正常工作。)

  3. (与您的情况无关,但我完全提及它:) Surface不是帧缓冲区,它是缓冲区队列。当您使用GLES提交缓冲区时,它已经消失,您无法再从中读取。因此,如果您使用GLES进行渲染并使用GLES捕获,则需要在调用eglSwapBuffers()之前读回像素。

  4. 使用Canvas渲染,最简单的方法是捕获&#34; Surface的内容就是简单地绘制两次。创建一个屏幕大小的位图,从位图创建一个Canvas,并将其传递给您的draw()函数。

    使用GLES渲染,您可以在缓冲区交换之前使用glReadPixels()来抓取像素。 Grafika中的抓取代码的实现(比问题中的代码便宜);请参阅EglSurfaceBase中的saveFrame()

    如果您将视频直接发送到Surface(通过MediaPlayer),则无法捕获帧,因为您的应用永远无法访问它们 - 它们直接从mediaserver转到合成器(SurfaceFlinger)。但是,您可以通过SurfaceTexture路由传入的帧,并从应用程序渲染两次,一次用于显示,一次用于捕获。有关详细信息,请参阅this question

    另一种方法是使用TextureView替换SurfaceView,它可以像任何其他Surface一样绘制。然后,您可以使用其中一个getBitmap()调用来捕获帧。 TextureView的效率低于SurfaceView,因此不建议在所有情况下使用它,但它很容易做到。

    如果您希望获得包含Surface内容和View UI内容的复合屏幕截图,您将需要捕获上面的Canvas,使用通常的绘图缓存技巧捕获View,然后手动合成两个。请注意,这不会获取系统部件(状态栏,导航栏)。

    在Lollipop及更高版本(API 21+)上的

    更新:,您可以使用MediaProjection类通过虚拟显示捕获整个屏幕。这种方法有一些权衡,例如你正在捕捉渲染的屏幕,而不是发送到Surface的帧,所以你得到的可能是向上或向下缩放以适合窗口。此外,此方法涉及Activity切换,因为您必须创建一个intent(通过在ProjectionManager对象上调用createScreenCaptureIntent)并等待其结果。

    如果您想详细了解所有这些内容的工作原理,请参阅Android System-Level Graphics Architecture doc。

答案 1 :(得分:2)

这是因为SurfaceView使用OpenGL线程进行绘制并直接绘制到硬件缓冲区。你必须使用glReadPixels(可能还有GLWrapper)。

见线程:Android OpenGL Screenshot

答案 2 :(得分:2)

我知道它的回复很晚,但是对于那些面临相同问题的人,

我们可以使用 PixelCopy 来获取快照。在API level 24及更高版本

中可用
PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());

哪里

surfaceViewObject是表面视图的对象

BitmapDest是用于保存图像的位图对象,并且不能为空

侦听器是OnPixelCopyFinishedListener

有关更多信息,请参考-https://developer.android.com/reference/android/view/PixelCopy

答案 3 :(得分:2)

Update 2020 view.setDrawingCacheEnabled(true)在API 28中已弃用

如果您使用的是普通视图,则可以使用指定的位图创建画布以将其绘制到其中。然后要求视图在该画布上绘制并返回由Canvas填充的位图

 /**
     * Copy View to Canvas and return bitMap
     */
    fun getBitmapFromView(view: View): Bitmap? {
        var bitmap =
            Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        view.draw(canvas)
        return bitmap
    }

或者您可以在使用视图绘制画布之前用默认颜色填充画布:

    /**
     * Copy View to Canvas and return bitMap and fill it with default color
     */
    fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
        var bitmap =
            Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        var canvas = Canvas(bitmap)
        canvas.drawColor(defaultColor)
        view.draw(canvas)
        return bitmap
    }

以上方法不适用于将其绘制为屏幕截图中的孔的表面视图。

对于自Android 24起的曲面视图,您需要使用Pixel Copy

    /**
     * Pixel copy to copy SurfaceView/VideoView into BitMap
     */
     fun usePixelCopy(videoView: SurfaceView,  callback: (Bitmap?) -> Unit) {
        val bitmap: Bitmap = Bitmap.createBitmap(
            videoView.width,
            videoView.height,
            Bitmap.Config.ARGB_8888
        );
        try {
        // Create a handler thread to offload the processing of the image.
        val handlerThread = HandlerThread("PixelCopier");
        handlerThread.start();
        PixelCopy.request(
            videoView, bitmap,
            PixelCopy.OnPixelCopyFinishedListener { copyResult ->
                if (copyResult == PixelCopy.SUCCESS) {
                    callback(bitmap)
                }
                handlerThread.quitSafely();
            },
            Handler(handlerThread.looper)
        )
        } catch (e: IllegalArgumentException) {
            callback(null)
            // PixelCopy may throw IllegalArgumentException, make sure to handle it
            e.printStackTrace()
        }
    }

这种方法可以截取Surface Vie的任何子类的屏幕截图,例如VideoView

Screenshot.usePixelCopy(videoView) { bitmap: Bitmap? ->
                processBitMap(bitmap)
            }

答案 4 :(得分:0)

这是使用 PixelCopy 截取表面视图屏幕截图的完整方法。它需要 API 24(Android N)。

@RequiresApi(api = Build.VERSION_CODES.N)
private void capturePicture() {
    Bitmap bmp = Bitmap.createBitmap(surfaceView.getWidth(), surfaceView.getHeight(), Bitmap.Config.ARGB_8888);
    PixelCopy.request(surfaceView, bmp, i -> {
        iv_Result.setImageBitmap(bmp); //"iv_Result" is the image view
    }, new Handler(Looper.getMainLooper()));
}