Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0449a405 authored by Mathias Agopian's avatar Mathias Agopian
Browse files

screenshots can now go into a Surface from java

A Surface can trivially be created from a SurfaceTexture.
Update ElectronBeam to use this new API.

Bug: 6940974
Change-Id: I20459443d0d853e3f8ae23104c08d185c336abea
parent 8edd7849
Loading
Loading
Loading
Loading
+69 −5
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import dalvik.system.CloseGuard;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.Surface;
import android.os.IBinder;
import android.os.SystemProperties;
import android.util.Log;
@@ -39,6 +40,8 @@ public class SurfaceControl {

    private static native Bitmap nativeScreenshot(IBinder displayToken,
            int width, int height, int minLayer, int maxLayer, boolean allLayers);
    private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
            int width, int height, int minLayer, int maxLayer, boolean allLayers);

    private static native void nativeOpenTransaction();
    private static native void nativeCloseTransaction();
@@ -525,9 +528,59 @@ public class SurfaceControl {
    }


    /**
     * Copy the current screen contents into the provided {@link Surface}
     *
     * @param display The display to take the screenshot of.
     * @param consumer The {@link Surface} to take the screenshot into.
     * @param width The desired width of the returned bitmap; the raw
     * screen will be scaled down to this size.
     * @param height The desired height of the returned bitmap; the raw
     * screen will be scaled down to this size.
     * @param minLayer The lowest (bottom-most Z order) surface layer to
     * include in the screenshot.
     * @param maxLayer The highest (top-most Z order) surface layer to
     * include in the screenshot.
     */
    public static void screenshot(IBinder display, Surface consumer,
            int width, int height, int minLayer, int maxLayer) {
        screenshot(display, consumer, width, height, minLayer, maxLayer, false);
    }

    /**
     * Copy the current screen contents into the provided {@link Surface}
     *
     * @param display The display to take the screenshot of.
     * @param consumer The {@link Surface} to take the screenshot into.
     * @param width The desired width of the returned bitmap; the raw
     * screen will be scaled down to this size.
     * @param height The desired height of the returned bitmap; the raw
     * screen will be scaled down to this size.
     */
    public static void screenshot(IBinder display, Surface consumer,
            int width, int height) {
        screenshot(display, consumer, width, height, 0, 0, true);
    }

    /**
     * Copy the current screen contents into the provided {@link Surface}
     *
     * @param display The display to take the screenshot of.
     * @param consumer The {@link Surface} to take the screenshot into.
     */
    public static void screenshot(IBinder display, Surface consumer) {
        screenshot(display, consumer, 0, 0, 0, 0, true);
    }


    /**
     * Copy the current screen contents into a bitmap and return it.
     *
     * CAVEAT: Versions of screenshot that return a {@link Bitmap} can
     * be extremely slow; avoid use unless absolutely necessary; prefer
     * the versions that use a {@link Surface} instead, such as
     * {@link SurfaceControl#screenshot(IBinder, Surface)}.
     *
     * @param width The desired width of the returned bitmap; the raw
     * screen will be scaled down to this size.
     * @param height The desired height of the returned bitmap; the raw
@@ -538,25 +591,36 @@ public class SurfaceControl {
     * include in the screenshot.
     * @return Returns a Bitmap containing the screen contents, or null
     * if an error occurs.
     *
     */
    public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
        // TODO: should take the display as a parameter
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
    }

    /**
     * Like {@link SurfaceControl#screenshot(int, int, int, int)} but includes all
     * Surfaces in the screenshot.
     *
     */
    public static Bitmap screenshot(int width, int height) {
        // TODO: should take the display as a parameter
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        return nativeScreenshot(displayToken, width, height, 0, 0, true);
    }
    
    private static void screenshot(IBinder display, Surface consumer,
            int width, int height, int minLayer, int maxLayer, boolean allLayers) {
        if (display == null) {
            throw new IllegalArgumentException("displayToken must not be null");
        }
        if (consumer == null) {
            throw new IllegalArgumentException("consumer must not be null");
        }
        nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers);
    }

    private static void checkHeadless() {
        if (HEADLESS) {
            throw new UnsupportedOperationException("Device is headless");
+22 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@
#include "android/graphics/Region.h"

#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/android_view_SurfaceSession.h>

#include <gui/Surface.h>
@@ -161,7 +162,7 @@ static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
    }
}

static jobject nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
    sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
    if (displayToken == NULL) {
@@ -199,6 +200,24 @@ static jobject nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenO
    return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}

static void nativeScreenshot(JNIEnv* env, jclass clazz,
        jobject displayTokenObj, jobject surfaceObj,
        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
    sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
    if (displayToken != NULL) {
        sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
        if (consumer != NULL) {
            if (allLayers) {
                minLayer = 0;
                maxLayer = -1;
            }
            ScreenshotClient::capture(
                    displayToken, consumer->getIGraphicBufferProducer(),
                    width, height, uint32_t(minLayer), uint32_t(maxLayer));
        }
    }
}

static void nativeOpenTransaction(JNIEnv* env, jclass clazz) {
    SurfaceComposerClient::openGlobalTransaction();
}
@@ -393,6 +412,8 @@ static JNINativeMethod sSurfaceControlMethods[] = {
    {"nativeDestroy", "(I)V",
            (void*)nativeDestroy },
    {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZ)Landroid/graphics/Bitmap;",
            (void*)nativeScreenshotBitmap },
    {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;IIIIZ)V",
            (void*)nativeScreenshot },
    {"nativeOpenTransaction", "()V",
            (void*)nativeOpenTransaction },
+54 −87
Original line number Diff line number Diff line
@@ -16,18 +16,20 @@

package com.android.server.power;

import com.android.server.display.DisplayManagerService;
import com.android.server.display.DisplayTransactionListener;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES10;
import android.opengl.GLUtils;
import android.opengl.GLES11Ext;
import android.os.Looper;
import android.util.FloatMath;
import android.util.Slog;
@@ -37,10 +39,8 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;

import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.android.server.display.DisplayManagerService;
import com.android.server.display.DisplayTransactionListener;

/**
 * Bzzzoooop!  *crackle*
@@ -94,6 +94,7 @@ final class ElectronBeam {
    // Texture names.  We only use one texture, which contains the screenshot.
    private final int[] mTexNames = new int[1];
    private boolean mTexNamesGenerated;
    private float mTexMatrix[] = new float[16];

    // Vertex and corresponding texture coordinates.
    // We have 4 2D vertices, so 8 elements.  The vertices form a quad.
@@ -115,6 +116,7 @@ final class ElectronBeam {
     */
    public static final int MODE_FADE = 2;


    public ElectronBeam(DisplayManagerService displayManager) {
        mDisplayManager = displayManager;
    }
@@ -264,19 +266,23 @@ final class ElectronBeam {
        GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
        GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);

        // set-up texturing
        GLES10.glDisable(GLES10.GL_TEXTURE_2D);
        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);

        // bind texture and set blending for drawing planes
        GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
        GLES10.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
        GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE,
                mMode == MODE_WARM_UP ? GLES10.GL_MODULATE : GLES10.GL_REPLACE);
        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE);
        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE);
        GLES10.glEnable(GLES10.GL_TEXTURE_2D);
        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
        GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer);
        GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);

@@ -296,7 +302,7 @@ final class ElectronBeam {
        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);

        // clean up after drawing planes
        GLES10.glDisable(GLES10.GL_TEXTURE_2D);
        GLES10.glDisable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
        GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
        GLES10.glColorMask(true, true, true, true);

@@ -371,14 +377,6 @@ final class ElectronBeam {
    }

    private boolean captureScreenshotTextureAndSetViewport() {
        // TODO: Use a SurfaceTexture to avoid the extra texture upload.
        Bitmap bitmap = SurfaceControl.screenshot(mDisplayWidth, mDisplayHeight,
                0, ELECTRON_BEAM_LAYER - 1);
        if (bitmap == null) {
            Slog.e(TAG, "Could not take a screenshot!");
            return false;
        }
        try {
        if (!attachEglContext()) {
            return false;
        }
@@ -391,46 +389,21 @@ final class ElectronBeam {
                mTexNamesGenerated = true;
            }

                GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
                if (checkGlErrors("glBindTexture")) {
                    return false;
                }

                float u = 1.0f;
                float v = 1.0f;
                GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0);
                if (checkGlErrors("glTexImage2D, first try", false)) {
                    // Try a power of two size texture instead.
                    int tw = nextPowerOfTwo(mDisplayWidth);
                    int th = nextPowerOfTwo(mDisplayHeight);
                    int format = GLUtils.getInternalFormat(bitmap);
                    GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0,
                            format, tw, th, 0,
                            format, GLES10.GL_UNSIGNED_BYTE, null);
                    if (checkGlErrors("glTexImage2D, second try")) {
                        return false;
                    }

                    GLUtils.texSubImage2D(GLES10.GL_TEXTURE_2D, 0, 0, 0, bitmap);
                    if (checkGlErrors("glTexSubImage2D")) {
                        return false;
                    }
            SurfaceTexture st = new SurfaceTexture(mTexNames[0]);
            SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                    SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN),
                    new Surface(st));

                    u = (float)mDisplayWidth / tw;
                    v = (float)mDisplayHeight / th;
                }
            st.updateTexImage();
            st.getTransformMatrix(mTexMatrix);

            // Set up texture coordinates for a quad.
            // We might need to change this if the texture ends up being
            // a different size from the display for some reason.
                mTexCoordBuffer.put(0, 0f);
                mTexCoordBuffer.put(1, v);
                mTexCoordBuffer.put(2, 0f);
                mTexCoordBuffer.put(3, 0f);
                mTexCoordBuffer.put(4, u);
                mTexCoordBuffer.put(5, 0f);
                mTexCoordBuffer.put(6, u);
                mTexCoordBuffer.put(7, v);
            mTexCoordBuffer.put(0, 0f); mTexCoordBuffer.put(1, 0f);
            mTexCoordBuffer.put(2, 0f); mTexCoordBuffer.put(3, 1f);
            mTexCoordBuffer.put(4, 1f); mTexCoordBuffer.put(5, 1f);
            mTexCoordBuffer.put(6, 1f); mTexCoordBuffer.put(7, 0f);

            // Set up our viewport.
            GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
@@ -441,12 +414,10 @@ final class ElectronBeam {
            GLES10.glLoadIdentity();
            GLES10.glMatrixMode(GLES10.GL_TEXTURE);
            GLES10.glLoadIdentity();
            GLES10.glLoadMatrixf(mTexMatrix, 0);
        } finally {
            detachEglContext();
        }
        } finally {
            bitmap.recycle();
        }
        return true;
    }

@@ -662,10 +633,6 @@ final class ElectronBeam {
        return 1.0f / (1.0f + FloatMath.exp(-x * s));
    }

    private static int nextPowerOfTwo(int value) {
        return 1 << (32 - Integer.numberOfLeadingZeros(value));
    }

    private static FloatBuffer createNativeFloatBuffer(int size) {
        ByteBuffer bb = ByteBuffer.allocateDirect(size * 4);
        bb.order(ByteOrder.nativeOrder());