Add TextureView.getBitmap()
This API can be used to get a Bitmap copy of the content of a
TextureView.
Change-Id: I07522216c353720fba5cab333174f58f484eb911
diff --git a/api/current.txt b/api/current.txt
index 3724d16..7c9c851 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21566,8 +21566,12 @@
ctor public TextureView(android.content.Context, android.util.AttributeSet);
ctor public TextureView(android.content.Context, android.util.AttributeSet, int);
method public final void draw(android.graphics.Canvas);
+ method public android.graphics.Bitmap getBitmap();
+ method public android.graphics.Bitmap getBitmap(int, int);
+ method public android.graphics.Bitmap getBitmap(android.graphics.Bitmap);
method public android.graphics.SurfaceTexture getSurfaceTexture();
method public android.view.TextureView.SurfaceTextureListener getSurfaceTextureListener();
+ method public boolean isAvailable();
method protected final void onDraw(android.graphics.Canvas);
method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener);
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index d5cad96..383bfb3 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -166,6 +166,7 @@
static native void nUpdateTextureLayer(int layerId, int width, int height, int surface);
static native void nDestroyLayer(int layerId);
static native void nDestroyLayerDeferred(int layerId);
+ static native boolean nCopyLayer(int layerId, int bitmap);
///////////////////////////////////////////////////////////////////////////
// Canvas management
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index bc191a6..69dfc2b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -17,6 +17,8 @@
package android.view;
+import android.graphics.Bitmap;
+
/**
* An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
*/
@@ -40,7 +42,10 @@
return mLayer;
}
-
+ boolean copyInto(Bitmap bitmap) {
+ return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
+ }
+
@Override
void destroy() {
if (mFinalizer != null) mFinalizer.destroy();
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 2611ec0..5944bd4 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,6 +17,7 @@
package android.view;
+import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
@@ -204,8 +205,18 @@
* @param surface The surface to update
*/
abstract void updateTextureLayer(HardwareLayer layer, int width, int height,
- SurfaceTexture surface);
-
+ SurfaceTexture surface);
+
+ /**
+ * Copies the content of the specified layer into the specified bitmap.
+ *
+ * @param layer The hardware layer to copy
+ * @param bitmap The bitmap to copy the layer into
+ *
+ * @return True if the copy was successful, false otherwise
+ */
+ abstract boolean copyLayer(HardwareLayer layer, Bitmap bitmap);
+
/**
* Initializes the hardware renderer for the specified surface and setup the
* renderer for drawing, if needed. This is invoked when the ViewAncestor has
@@ -814,6 +825,11 @@
((GLES20TextureLayer) layer).update(width, height, surface.mSurfaceTexture);
}
+ @Override
+ boolean copyLayer(HardwareLayer layer, Bitmap bitmap) {
+ return ((GLES20Layer) layer).copyInto(bitmap);
+ }
+
static HardwareRenderer create(boolean translucent) {
if (GLES20Canvas.isAvailable()) {
return new Gl20Renderer(translucent);
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index bc1ad3c..4daa892 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -17,6 +17,7 @@
package android.view;
import android.content.Context;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.SurfaceTexture;
@@ -89,6 +90,8 @@
* @see SurfaceTexture
*/
public class TextureView extends View {
+ private static final String LOG_TAG = "TextureView";
+
private HardwareLayer mLayer;
private SurfaceTexture mSurface;
private SurfaceTextureListener mListener;
@@ -148,7 +151,7 @@
super.onAttachedToWindow();
if (!isHardwareAccelerated()) {
- Log.w("TextureView", "A TextureView or a subclass can only be "
+ Log.w(LOG_TAG, "A TextureView or a subclass can only be "
+ "used with hardware acceleration enabled.");
}
}
@@ -293,8 +296,95 @@
}
/**
+ * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+ * of the associated surface texture. If the surface texture is not available,
+ * this method returns null.</p>
+ *
+ * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+ * pixel format and its dimensions are the same as this view's.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+ * texture is not available or the width <= 0 or the height <= 0
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(android.graphics.Bitmap)
+ * @see #getBitmap(int, int)
+ */
+ public Bitmap getBitmap() {
+ return getBitmap(getWidth(), getHeight());
+ }
+
+ /**
+ * <p>Returns a {@link android.graphics.Bitmap} representation of the content
+ * of the associated surface texture. If the surface texture is not available,
+ * this method returns null.</p>
+ *
+ * <p>The bitmap returned by this method uses the {@link Bitmap.Config#ARGB_8888}
+ * pixel format.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * @param width The width of the bitmap to create
+ * @param height The height of the bitmap to create
+ *
+ * @return A valid {@link Bitmap.Config#ARGB_8888} bitmap, or null if the surface
+ * texture is not available or width is <= 0 or height is <= 0
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(android.graphics.Bitmap)
+ * @see #getBitmap()
+ */
+ public Bitmap getBitmap(int width, int height) {
+ if (isAvailable() && width > 0 && height > 0) {
+ return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
+ }
+ return null;
+ }
+
+ /**
+ * <p>Copies the content of this view's surface texture into the specified
+ * bitmap. If the surface texture is not available, the copy is not executed.
+ * The content of the surface texture will be scaled to fit exactly inside
+ * the specified bitmap.</p>
+ *
+ * <p><strong>Do not</strong> invoke this method from a drawing method
+ * ({@link #onDraw(android.graphics.Canvas)} for instance).</p>
+ *
+ * @param bitmap The bitmap to copy the content of the surface texture into,
+ * cannot be null, all configurations are supported
+ *
+ * @return The bitmap specified as a parameter
+ *
+ * @see #isAvailable()
+ * @see #getBitmap(int, int)
+ * @see #getBitmap()
+ */
+ public Bitmap getBitmap(Bitmap bitmap) {
+ if (bitmap != null && isAvailable()) {
+ mAttachInfo.mHardwareRenderer.copyLayer(mLayer, bitmap);
+ }
+ return bitmap;
+ }
+
+ /**
+ * Returns true if the {@link SurfaceTexture} associated with this
+ * TextureView is available for rendering. When this method returns
+ * true, {@link #getSurfaceTexture()} returns a valid surface texture.
+ */
+ public boolean isAvailable() {
+ return mSurface != null;
+ }
+
+ /**
* Returns the {@link SurfaceTexture} used by this view. This method
- * may return null if the view is not attached to a window.
+ * may return null if the view is not attached to a window or if the surface
+ * texture has not been initialized yet.
+ *
+ * @see #isAvailable()
*/
public SurfaceTexture getSurfaceTexture() {
return mSurface;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 57a97bd..7e82efb 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -667,6 +667,11 @@
renderer->drawLayer(layer, x, y, paint);
}
+static jboolean android_view_GLES20Canvas_copyLayer(JNIEnv* env, jobject clazz,
+ Layer* layer, SkBitmap* bitmap) {
+ return LayerRenderer::copyLayer(layer, bitmap);
+}
+
#endif // USE_OPENGL_RENDERER
// ----------------------------------------------------------------------------
@@ -792,6 +797,7 @@
{ "nDestroyLayer", "(I)V", (void*) android_view_GLES20Canvas_destroyLayer },
{ "nDestroyLayerDeferred", "(I)V", (void*) android_view_GLES20Canvas_destroyLayerDeferred },
{ "nDrawLayer", "(IIFFI)V", (void*) android_view_GLES20Canvas_drawLayer },
+ { "nCopyLayer", "(II)Z", (void*) android_view_GLES20Canvas_copyLayer },
#endif
};
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index f316ba7..146e789 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -315,5 +315,95 @@
}
}
+bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
+ Caches& caches = Caches::getInstance();
+ if (layer && layer->isTextureLayer && bitmap->width() <= caches.maxTextureSize &&
+ bitmap->height() <= caches.maxTextureSize) {
+
+ GLuint fbo = caches.fboCache.get();
+ if (!fbo) {
+ LOGW("Could not obtain an FBO");
+ return false;
+ }
+
+ GLuint texture;
+ GLuint previousFbo;
+
+ GLenum format;
+ GLenum type;
+
+ switch (bitmap->config()) {
+ case SkBitmap::kA8_Config:
+ format = GL_ALPHA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ format = GL_RGB;
+ type = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case SkBitmap::kARGB_4444_Config:
+ format = GL_RGBA;
+ type = GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ default:
+ format = GL_RGBA;
+ type = GL_UNSIGNED_BYTE;
+ break;
+ }
+
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ glGenTextures(1, &texture);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, format, bitmap->width(), bitmap->height(),
+ 0, format, type, NULL);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, texture, 0);
+
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ float alpha = layer->alpha;
+ SkXfermode::Mode mode = layer->mode;
+
+ layer->mode = SkXfermode::kSrc_Mode;
+ layer->alpha = 255;
+ layer->fbo = fbo;
+
+ LayerRenderer renderer(layer);
+ renderer.setViewport(bitmap->width(), bitmap->height());
+ renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
+ bitmap->width(), bitmap->height(), !layer->blend);
+
+ Rect bounds;
+ bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height());
+ renderer.drawTextureLayer(layer, bounds);
+
+ SkAutoLockPixels alp(*bitmap);
+ glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels());
+
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ layer->mode = mode;
+ layer->alpha = alpha;
+ layer->fbo = 0;
+ glDeleteTextures(1, &texture);
+ caches.fboCache.put(fbo);
+
+ return true;
+ }
+ return false;
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 59cab96..797dfc6 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -20,6 +20,8 @@
#include "OpenGLRenderer.h"
#include "Layer.h"
+#include <SkBitmap.h>
+
namespace android {
namespace uirenderer {
@@ -60,6 +62,7 @@
GLenum renderTarget, float* transform);
static void destroyLayer(Layer* layer);
static void destroyLayerDeferred(Layer* layer);
+ static bool copyLayer(Layer* layer, SkBitmap* bitmap);
private:
void generateMesh();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b9e3ddc..0a3d5090 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -178,6 +178,14 @@
return 0;
}
+ /**
+ * Renders the specified layer as a textured quad.
+ *
+ * @param layer The layer to render
+ * @param rect The bounds of the layer
+ */
+ void drawTextureLayer(Layer* layer, const Rect& rect);
+
private:
/**
* Saves the current state of the renderer as a new snapshot.
@@ -256,14 +264,6 @@
void clearLayerRegions();
/**
- * Renders the specified layer as a textured quad.
- *
- * @param layer The layer to render
- * @param rect The bounds of the layer
- */
- void drawTextureLayer(Layer* layer, const Rect& rect);
-
- /**
* Mark the layer as dirty at the specified coordinates. The coordinates
* are transformed with the supplied matrix.
*/
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 01d30eb..3e7ca08 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />