Android 截取 Surface View 的屏幕截图显示黑屏 [英] Android Take Screenshot of Surface View Shows Black Screen

查看:90
本文介绍了Android 截取 Surface View 的屏幕截图显示黑屏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am attempting to Take a Screenshot of my Game through code and Share it through an Intent. I able to do of those things, however the screenshot always appears black. Here is the Code Related to Sharing the Screenshot:

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

这是在 MainActivity 中:

This is in the 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

这就是我绘制所有内容的方式:

And this is how I am Drawing everything:

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

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

Ok, based on some answers, i have constructed this:

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

游戏视图包含以下方法:

The Gameview contains the Following Method:

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

屏幕截图仍然是黑色的.我保存它的方式有问题吗?

The Screenshot is still Black. Is there something wrong with the way I am saving it perhaps?

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

I have attempted several different methods to take the Screenshot, but none of them worked: The one shown in the code above was the most commonly suggested one. But it does not seem to work. Is this an Issue with using SurfaceView? And if so, why does view.getDrawingCache(true) even exist if I cant use it and how do I fix this?

我的代码:

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

谢谢.

推荐答案

对此有很多困惑,还有一些 正确 答案.

There is a great deal of confusion about this, and a few correct answers.

交易如下:

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

  1. A SurfaceView has two parts, the Surface and the View. The Surface is on a completely separate layer from all of the View UI elements. The getDrawingCache() approach works on the View layer only, so it doesn't capture anything on the Surface.

缓冲队列有一个生产者-消费者API,它只能有一个生产者.Canvas 是一个生产商,GLES 是另一个.你不能用 Canvas 绘图,也不能用 GLES 读取像素.(从技术上讲,如果 Canvas 正在使用 GLES 并且当您读取像素时正确的 EGL 上下文是当前的,则您可以,但这不能保证.在任何发布的版本中都不会加速向 Surface 渲染的 Canvas的 Android 版本,所以现在没有希望它可以工作.)

The buffer queue has a producer-consumer API, and it can have only one producer. Canvas is one producer, GLES is another. You can't draw with Canvas and read pixels with GLES. (Technically, you could if the Canvas were using GLES and the correct EGL context was current when you went to read the pixels, but that's not guaranteed. Canvas rendering to a Surface is not accelerated in any released version of Android, so right now there's no hope of it working.)

(与您的情况无关,但为了完整性我会提到它:)Surface 不是帧缓冲区,而是缓冲区队列.当您使用 GLES 提交缓冲区时,它就消失了,您无法再从中读取.因此,如果您使用 GLES 渲染并使用 GLES 捕获,则需要在调用 eglSwapBuffers() 之前读取像素.

(Not relevant for your case, but I'll mention it for completeness:) A Surface is not a frame buffer, it is a queue of buffers. When you submit a buffer with GLES, it is gone, and you can no longer read from it. So if you were rendering with GLES and capturing with GLES, you would need to read the pixels back before calling eglSwapBuffers().

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

With Canvas rendering, the easiest way to "capture" the Surface contents is to simply draw it twice. Create a screen-sized Bitmap, create a Canvas from the Bitmap, and pass it to your draw() function.

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

With GLES rendering, you can use glReadPixels() before the buffer swap to grab the pixels. There's a (less-expensive than the code in the question) implementation of the grab code in Grafika; see saveFrame() in EglSurfaceBase.

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

If you were sending video directly to a Surface (via MediaPlayer) there would be no way to capture the frames, because your app never has access to them -- they go directly from mediaserver to the compositor (SurfaceFlinger). You can, however, route the incoming frames through a SurfaceTexture, and render them twice from your app, once for display and once for capture. See this question for more info.

另一种选择是用 TextureView 替换 SurfaceView,它可以像任何其他 Surface 一样绘制.然后,您可以使用 getBitmap() 调用以捕获帧.TextureView 的效率低于 SurfaceView,因此不建议在所有情况下使用它,但它很简单.

One alternative is to replace the SurfaceView with a TextureView, which can be drawn on like any other Surface. You can then use one of the getBitmap() calls to capture a frame. TextureView is less efficient than SurfaceView, so this is not recommended for all situations, but it's straightforward to do.

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

If you were hoping to get a composite screen shot containing both the Surface contents and the View UI contents, you will need to capture the Canvas as above, capture the View with the usual drawing cache trick, and then composite the two manually. Note this won't pick up the system parts (status bar, nav bar).

更新:在 Lollipop 及更高版本 (API 21+) 上,您可以使用 MediaProjection 类以使用虚拟显示捕获整个屏幕.这种方法有一些权衡,例如您正在捕获渲染的屏幕,而不是发送到 Surface 的帧,因此您获得的内容可能已被放大或缩小以适应窗口.此外,此方法涉及 Activity 切换,因为您必须创建一个意图(通过在 ProjectionManager 对象上调用 createScreenCaptureIntent)并等待其结果.

Update: on Lollipop and later (API 21+) you can use the MediaProjection class to capture the entire screen with a virtual display. There are some trade-offs with this approach, e.g. you're capturing the rendered screen, not the frame that was sent to the Surface, so what you get may have been up- or down-scaled to fit the window. In addition, this approach involves an Activity switch since you have to create an intent (by calling createScreenCaptureIntent on the ProjectionManager object) and wait for its result.

如果您想详细了解所有这些东西的工作原理,请参阅 Android 系统-关卡图形架构 doc.

If you want to learn more about how all this stuff works, see the Android System-Level Graphics Architecture doc.

这篇关于Android 截取 Surface View 的屏幕截图显示黑屏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆