web-dev-qa-db-fra.com

Android Take Capture d'écran de Surface View Shows Black Screen

J'essaie de prendre une capture d'écran de mon jeu avec du code et de la partager avec une intention. Je suis capable de faire ces choses, cependant la capture d'écran apparaît toujours en noir. Voici le code associé au partage de la capture d'écran:

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

C'est dans la MainActivity: 

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

public static SurfaceView getView() {
    return view;
}

Et la vue elle-même:

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

Et voici comment je dessine tout:

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

Ok, sur la base de certaines réponses, j'ai construit ceci:

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

Le Gameview contient la méthode suivante:

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

La capture d'écran est toujours noire. Y at-il quelque chose qui cloche dans la façon dont je le sauve peut-être?

J'ai essayé plusieurs méthodes différentes pour prendre une capture d'écran, mais aucune d'entre elles n'a fonctionné: La méthode présentée dans le code ci-dessus est la méthode la plus couramment suggérée. Mais cela ne semble pas fonctionner ... Est-ce un problème d'utilisation de SurfaceView? Et si oui, pourquoi view.getDrawingCache (true) existe-t-il même si je ne peux pas l'utiliser et comment puis-je résoudre ce problème?

Mon code:

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

Je vous remercie.

26
TastyLemons

Il y a beaucoup de confusion à ce sujet, et quelques correct _ { réponses .

Voici le deal:

  1. Une SurfaceView comprend deux parties, la surface et la vue. La surface se trouve sur un calque complètement distinct de tous les éléments de l'interface de visualisation. L'approche getDrawingCache() fonctionne uniquement sur le calque View, elle ne capture donc rien sur la surface.

  2. La file d'attente de mémoire tampon a une API producteur-consommateur et ne peut avoir qu'un seul producteur. Canvas est un producteur, GLES en est un autre. Vous ne pouvez pas dessiner avec Canvas et lire des pixels avec GLES. (Techniquement, vous pourriez si le canevas utilisait GLES et que le contexte EGL correct était courant lorsque vous lisiez les pixels, mais cela n'est pas garanti. Le rendu du canevas sur une surface n'est accéléré dans aucune version publiée d'Android. donc pour le moment il n'y a aucun espoir que cela fonctionne.)

  3. (Non pertinent pour votre cas, mais je le mentionnerai pour plus de précision :) Une surface n'est pas un tampon de trame, c'est une file d'attente de tampons. Lorsque vous soumettez un tampon avec GLES, il est parti et vous ne pouvez plus le lire. Ainsi, si vous réalisez un rendu avec GLES et une capture avec GLES, vous devez relire les pixels avant d'appeler eglSwapBuffers().

Avec le rendu Canvas, le moyen le plus simple de "capturer" le contenu de la surface est de le dessiner deux fois. Créez un bitmap de la taille d'un écran, créez un canevas à partir du bitmap et transmettez-le à votre fonction draw().

Avec le rendu GLES, vous pouvez utiliser glReadPixels() avant l'échange de mémoire tampon pour capturer les pixels. Il y a une implémentation (moins coûteuse que le code dans la question) du code saisi dans Grafika ; voir saveFrame() dans EglSurfaceBase .

Si vous envoyiez une vidéo directement à une Surface (via MediaPlayer), il serait impossible de capturer les images, car votre application n'y a jamais accès. Elles passent directement du serveur multimédia au compositeur (SurfaceFlinger). Vous pouvez toutefois router les images entrantes via une SurfaceTexture et les restituer deux fois à partir de votre application, une fois pour l'affichage et une fois pour la capture. Voir cette question _ pour plus d'informations.

Une alternative consiste à remplacer le SurfaceView par un TextureView, qui peut être dessiné sur n'importe quelle surface. Vous pouvez ensuite utiliser l'un des appels getBitmap() pour capturer une image. TextureView est moins efficace que SurfaceView, ce n'est donc pas recommandé dans toutes les situations, mais c'est simple à faire.

Si vous espériez obtenir une capture d'écran composite contenant à la fois le contenu de la surface et celui de l'interface utilisateur, vous devez capturer le canevas comme ci-dessus, capturer la vue avec l'astuce habituelle du cache de dessin, puis combiner les deux manuellement. Notez que cela ne captera pas les composants du système (barre d'état, barre de navigation).

Update: sur Lollipop et les versions ultérieures (API 21+), vous pouvez utiliser la classe MediaProjection pour capturer la totalité de l'écran avec un affichage virtuel. Il existe certains compromis avec cette approche, par exemple vous capturez l'écran affiché, pas le cadre qui a été envoyé à la surface, de sorte que ce que vous obtenez a peut-être été réduit ou agrandi pour s'adapter à la fenêtre. De plus, cette approche implique un commutateur d’activité puisque vous devez créer une intention (en appelant createScreenCaptureIntent sur l’objet ProjectionManager) et attendre son résultat.

Si vous souhaitez en savoir plus sur le fonctionnement de tout cela, consultez l’Android Architecture graphique de niveau système doc.

51
fadden

En effet, SurfaceView utilise un thread OpenGL pour le dessin et s’appuie directement sur un tampon matériel. Vous devez utiliser glReadPixels (et probablement un GLWrapper).

Voir le fil: Android OpenGL Screenshot

2
Zielony

Je sais que sa réponse est tardive, mais pour ceux qui font face au même problème, 

nous pouvons utiliser PixelCopy pour récupérer l'instantané. Il est disponible dans API level 24 et supérieur 

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

où,

surfaceViewObject est l'objet de la vue de surface 

BitmapDest est l'objet bitmap où l'image sera sauvegardée et où elle ne pourra pas être nulle

listener est OnPixelCopyFinishedListener

pour plus d'informations, consultez - https://developer.Android.com/reference/Android/view/PixelCopy

1
Anjani Mittal