簡體   English   中英

Android 截取 Surface View 的屏幕截圖顯示黑屏

[英]Android Take Screenshot of Surface View Shows Black Screen

我正在嘗試通過代碼截取我的游戲截圖並通過意圖共享它。 我可以做這些事情,但是屏幕截圖總是顯示為黑色。 這是與共享屏幕截圖相關的代碼:

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

謝謝你。

關於這一點有很多困惑,還有一些正確的答案

這是交易:

  1. SurfaceView 有兩個部分,Surface 和 View。 Surface 位於與所有 View UI 元素完全獨立的層上。 getDrawingCache()方法僅適用於 View 層,因此它不會捕獲 Surface 上的任何內容。

  2. 緩沖隊列有一個生產者-消費者API,它只能有一個生產者。 Canvas 是一個生產商,GLES 是另一個。 你不能用 Canvas 畫畫,也不能用 GLES 讀取像素。 (從技術上講,你可以當畫布使用GLES和正確的EGL背景是當前當你去閱讀的像素,但是這不能保證。畫布渲染到表面Android中的任何發布的版本不加速,所以現在有沒有希望它起作用。)

  3. (與您的情況無關,但為了完整起見,我會提到它:) Surface 不是幀緩沖區,而是緩沖區隊列。 當您使用 GLES 提交緩沖區時,它就消失了,您無法再從中讀取。 因此,如果您使用 GLES 渲染並使用 GLES 捕獲,則需要在調用eglSwapBuffers()之前讀取像素。

使用 Canvas 渲染,“捕獲” Surface 內容的最簡單方法是簡單地繪制兩次。 創建一個屏幕大小的位圖,從位圖創建一個畫布,並將其傳遞給您的draw()函數。

使用 GLES 渲染,您可以在緩沖區交換之前使用glReadPixels()來抓取像素。 Grafika 中有一個(比問題中的代碼便宜)抓取代碼的實現 saveFrame()EglSurfaceBase

如果您將視頻直接發送到 Surface(通過 MediaPlayer),將無法捕獲幀,因為您的應用程序永遠無法訪問它們——它們直接從媒體服務器發送到合成器 (SurfaceFlinger)。 但是,您可以通過 SurfaceTexture 路由傳入幀,並從您的應用程序渲染它們兩次,一次用於顯示,一次用於捕獲。 有關更多信息,請參閱此問題

一種替代方法是用 TextureView 替換 SurfaceView,它可以像任何其他 Surface 一樣繪制。 然后,您可以使用getBitmap()調用之一來捕獲幀。 TextureView 的效率低於 SurfaceView,因此不建議在所有情況下使用它,但這樣做很簡單。

如果你希望得到一個包含 Surface 內容和 View UI 內容的合成屏幕截圖,你需要像上面一樣捕獲 Canvas,使用通常的繪圖緩存技巧捕獲 View,然后手動合成兩者。 請注意,這不會拾取系統部件(狀態欄、導航欄)。

更新:在 Lollipop 及更高版本 (API 21+) 上,您可以使用MediaProjection類通過虛擬顯示器捕獲整個屏幕。 這種方法有一些權衡,例如,您正在捕獲渲染的屏幕,而不是發送到 Surface 的幀,因此您得到的可能已被放大或縮小以適應窗口。 此外,此方法涉及 Activity 切換,因為您必須創建一個意圖(通過在 ProjectionManager 對象上調用 createScreenCaptureIntent)並等待其結果。

如果您想了解有關所有這些內容如何工作的更多信息,請參閱 Android 系統級圖形架構文檔。

我知道這是一個遲到的回復,但對於那些面臨同樣問題的人來說,

我們可以使用PixelCopy來獲取快照。 它在API level 24及以上可用

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

在哪里,

surfaceViewObject 是表面視圖的對象

BitmapDest 是保存圖片的位圖對象,不能為空

偵聽器是 OnPixelCopyFinishedListener

有關更多信息,請參閱 - https://developer.android.com/reference/android/view/PixelCopy

這是因為 SurfaceView 使用 OpenGL 線程進行繪制並直接繪制到硬件緩沖區。 您必須使用 glReadPixels(可能還有 GLWrapper)。

見線程: Android OpenGL Screenshot

更新 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)
            }

這是使用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()));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM