【问题标题】:Android Take Screenshot of Surface View Shows Black ScreenAndroid 截图 Surface View 显示黑屏
【发布时间】:2015-01-07 10:41:06
【问题描述】:

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

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

谢谢。

【问题讨论】:

  • 我的代码基于该帖子上已接受的答案。还是不行
  • 点击here获取答案。它会给你帮助:)
  • @TastyLemons 请删除“工作代码:”字样。它误导了读者。

标签: android surfaceview


【解决方案1】:

对此有很多困惑,还有一些correctanswers

这是交易:

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 所以只是为了澄清一下,我究竟如何将画布存储到位图中?
  • 您创建一个位图,然后将其传递给 Canvas 构造函数 -- developer.android.com/reference/android/graphics/…
  • 我有一个相对布局,在该布局下有一个表面视图,其中有相机调用,在它上面有一个图像视图,可以增强相机。如何截取整个场景的屏幕截图?
  • @navinpai 请通过创建新问题来提问,而不是在答案中添加评论。见stackoverflow.com/questions/26545970/…
  • 那么如何在给定活动根视图的情况下截取该死的屏幕截图
【解决方案2】:

我知道这是一个迟到的回复,但对于那些面临同样问题的人来说,

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

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

在哪里,

surfaceViewObject 是表面视图的对象

BitmapDest是要保存图片的位图对象,不能为空

监听器是 OnPixelCopyFinishedListener

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

【讨论】:

  • 您对 24 岁以前有效的类似产品有什么建议吗?
  • 不,我尝试了很多解决方案,但在表面视图的情况下,只有这个有效。
  • 那么这适用于单个 Surface 视图吗?如果当前显示由表面视图和普通 Android UI 层组成怎么办?
  • 这只是表面视图,表面视图和其他内容我没有那个场景。
  • @AnjaniMittal 26级新增了一个可以捕获窗口的api,developer.android.com/reference/android/view/…
【解决方案3】:

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

    这是使用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 -> {
            imageView.setImageBitmap(bmp); //"iv_Result" is the image view
        }, new Handler(Looper.getMainLooper()));
    }
    

    【讨论】:

      【解决方案5】:

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

      查看帖子:Android OpenGL Screenshot

      【讨论】:

      • 很棒的链接。已接受答案中的代码似乎需要这样:GL10 gl where do I create this Object in my SurfaceView?
      • 当我阅读您的问题时,我确信您的方法应该有效。 stackoverflow 上有一个关于它的链接:stackoverflow.com/questions/4742868/… 我的方法使用 GLSurfaceView 和 GLWrapper。见:stackoverflow.com/questions/5979069/…
      • 更新的代码,见问题。它仍然无法正常工作。
      • 使用 Canvas 渲染到 Surface 永远不会进行硬件加速(从 5.0 开始),因此尝试使用 GLES 捕获像素将无济于事。如果启用了硬件加速,则渲染到视图使用 GLES,但习惯的绘图缓存方法更可靠。
      猜你喜欢
      • 2015-03-05
      • 1970-01-01
      • 1970-01-01
      • 2016-10-24
      • 2013-12-09
      • 1970-01-01
      • 2014-03-12
      相关资源
      最近更新 更多