Подтвердить что ты не робот

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

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

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

Спасибо.

4b9b3361

Ответ 1

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

Здесь сделка:

  • A SurfaceView имеет две части: поверхность и вид. Поверхность находится на совершенно отдельном уровне от всех элементов View UI. Подход getDrawingCache() работает только на уровне представления, поэтому он не захватывает ничего на поверхности.

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

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

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

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

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

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

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

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

Если вы хотите узнать больше о том, как все это работает, см. раздел Архитектура графического уровня на уровне системы doc.

Ответ 2

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

Смотрите тему: Android OpenGL Screenshot