Скриншот Android Take из Surface View показывает черный экран

android surfaceview

32200 просмотра

3 ответа

Я пытаюсь сделать снимок экрана моей игры с помощью кода и поделиться им через намерение. Я могу сделать из этих вещей, однако скриншот всегда выглядит черным. Вот код, связанный с обменом снимками экрана:

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

Спасибо.

Автор: TastyLemons Источник Размещён: 12.11.2019 09:26

Ответы (3)


56 плюса

Решение

В этом много путаницы и несколько правильных ответов .

Вот сделка:

  1. SurfaceView состоит из двух частей: Surface и View. Поверхность находится на совершенно отдельном слое от всех элементов пользовательского интерфейса View. getDrawingCache()Подход работает только на Вид слоя, так что это не захват что - либо на поверхности.

  2. Буферная очередь имеет API производителя-потребителя и может иметь только одного производителя. Canvas - один производитель, GLES - другой. Вы не можете рисовать с помощью Canvas и читать пиксели с помощью GLES. (Технически, вы могли бы это сделать, если Canvas использовал GLES и правильный контекст EGL был актуален, когда вы читали пиксели, но это не гарантировано. Рендеринг Canvas на поверхность не ускоряется ни в одной выпущенной версии Android, поэтому сейчас есть нет надежды на то, что это работает.)

  3. (Не относится к вашему случаю, но я упомяну это для полноты :) Поверхность - это не буфер кадров, а очередь буферов. Когда вы отправляете буфер с GLES, он исчезает, и вы больше не можете читать из него. Поэтому, если вы выполняете рендеринг с помощью GLES и захватываете с помощью GLES, вам необходимо прочитать пиксели обратно перед вызовом eglSwapBuffers().

С рендерингом Canvas, самый простой способ «захватить» содержимое Surface - просто нарисовать его дважды. Создайте растровое изображение размером с экран, создайте Canvas из растрового изображения и передайте его своей draw()функции.

С рендерингом GLES вы можете использовать glReadPixels()перед заменой буфера, чтобы захватить пиксели. Там есть (менее дорогой , чем код в вопросе) выполнение кода захвата в GRAFIKA ; см. saveFrame()в EglSurfaceBase .

Если вы отправляете видео непосредственно на Surface (через MediaPlayer), невозможно будет захватывать кадры, потому что ваше приложение никогда не имеет к ним доступа - они напрямую переходят с медиасервера на композитор (SurfaceFlinger). Однако вы можете направить входящие кадры через SurfaceTexture и дважды отобразить их из вашего приложения, один раз для отображения и один раз для захвата. Смотрите этот вопрос для получения дополнительной информации.

Один из вариантов - заменить SurfaceView на TextureView, который можно нарисовать как любую другую поверхность. Затем вы можете использовать один из getBitmap()вызовов для захвата кадра. TextureView менее эффективен, чем SurfaceView, так что это не рекомендуется для всех ситуаций, но это легко сделать.

Если вы надеялись получить комбинированный снимок экрана, содержащий как содержимое Surface, так и содержимое пользовательского интерфейса View, вам нужно будет захватить Canvas, как описано выше, захватить View с помощью обычного трюка с кешем рисования, а затем объединить их вручную. Обратите внимание, что это не подхватит части системы (строка состояния, панель навигации).

Обновление: в Lollipop и более поздних версиях (API 21+) вы можете использовать класс MediaProjection для захвата всего экрана виртуальным дисплеем. При таком подходе есть некоторые компромиссы, например, вы снимаете визуализированный экран, а не кадр, который был отправлен на поверхность, поэтому то, что вы получаете, может быть увеличено или уменьшено в соответствии с размером окна. Кроме того, в этом подходе используется переключатель Activity, поскольку необходимо создать намерение (путем вызова createScreenCaptureIntent для объекта ProjectionManager) и дождаться его результата.

Если вы хотите узнать больше о том, как все это работает, обратитесь к документации по графической архитектуре на уровне системы Android .

Автор: fadden Размещён: 07.01.2015 04:43

3 плюса

Я знаю, что это поздний ответ, но для тех, кто сталкивается с той же проблемой,

мы можем использовать PixelCopy для получения снимка. Это доступно в API level 24и выше

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

где,

surfaceViewObject - это объект вида поверхности

BitmapDest - это растровый объект, в котором изображение будет сохранено, и оно не может быть нулевым

слушатель OnPixelCopyFinishedListener

за дополнительной информацией обращайтесь - https://developer.android.com/reference/android/view/PixelCopy

Автор: Anjani Mittal Размещён: 22.11.2018 09:44

2 плюса

Это потому, что SurfaceView использует поток OpenGL для рисования и рисует непосредственно в аппаратный буфер. Вы должны использовать glReadPixels (и, вероятно, GLWrapper).

Смотрите ветку: Скриншот Android OpenGL

Автор: Zielony Размещён: 07.01.2015 10:49
Вопросы из категории :
32x32