Support larger bitmaps in BitmapFactory.Options.inBitmap
bug:8121994
Adds a new distiction between bitmap size and the allocation
(pixel ref/buffer) used to store its data.
BitmapFactory.inBitmap will allow a bitmap to be reinitialized with
new data if the bitmap being decoded is (after sampleSize) equal or
smaller.
Change-Id: I747750a735c858882df3af74fca6cdc46f2a9f81
diff --git a/api/current.txt b/api/current.txt
index 96a8481..08008667e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8633,6 +8633,7 @@
method public void eraseColor(int);
method public android.graphics.Bitmap extractAlpha();
method public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+ method public final int getAllocationByteCount();
method public final int getByteCount();
method public final android.graphics.Bitmap.Config getConfig();
method public int getDensity();
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index daabce3..d6549a1 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -211,19 +211,17 @@
SkBitmap* bitmap;
bool useExistingBitmap = false;
+ unsigned int existingBufferSize = 0;
if (javaBitmap == NULL) {
bitmap = new SkBitmap;
} else {
- if (sampleSize != 1) {
- return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
- }
-
bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
- // only reuse the provided bitmap if it is immutable
+ // only reuse the provided bitmap if it is mutable
if (!bitmap->isImmutable()) {
useExistingBitmap = true;
// config of supplied bitmap overrules config set in options
prefConfig = bitmap->getConfig();
+ existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
} else {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
bitmap = new SkBitmap;
@@ -252,6 +250,26 @@
decodeMode = SkImageDecoder::kDecodeBounds_Mode;
}
+ if (javaBitmap != NULL) {
+ // If we're reusing the pixelref from an existing bitmap, decode the bounds and
+ // reinitialize the native object for the new content, keeping the pixelRef
+ SkPixelRef* pixelRef = bitmap->pixelRef();
+ SkSafeRef(pixelRef);
+
+ SkBitmap boundsBitmap;
+ decoder->decode(stream, &boundsBitmap, prefConfig, SkImageDecoder::kDecodeBounds_Mode);
+ stream->rewind();
+
+ if (boundsBitmap.getSize() > existingBufferSize) {
+ return nullObjectReturn("bitmap marked for reuse too small to contain decoded data");
+ }
+
+ bitmap->setConfig(boundsBitmap.config(), boundsBitmap.width(), boundsBitmap.height(), 0);
+ bitmap->setPixelRef(pixelRef);
+ SkSafeUnref(pixelRef);
+ GraphicsJNI::reinitBitmap(env, javaBitmap);
+ }
+
SkBitmap* decoded;
if (willScale) {
decoded = new SkBitmap;
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 191b54a..7c420ad 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -152,6 +152,8 @@
static jclass gBitmap_class;
static jfieldID gBitmap_nativeInstanceID;
static jmethodID gBitmap_constructorMethodID;
+static jmethodID gBitmap_reinitMethodID;
+static jmethodID gBitmap_getAllocationByteCountMethodID;
static jclass gBitmapConfig_class;
static jfieldID gBitmapConfig_nativeInstanceID;
@@ -363,6 +365,15 @@
return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density);
}
+void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap)
+{
+ env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID);
+}
+
+int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap)
+{
+ return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
+}
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
{
@@ -584,6 +595,8 @@
gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
"(I[BZ[B[II)V");
+ gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "()V");
+ gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V");
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index c5b06f5..69b67cb 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -59,6 +59,10 @@
static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
jbyteArray ninepatch, int density = -1);
+ static void reinitBitmap(JNIEnv* env, jobject javaBitmap);
+
+ static int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap);
+
static jobject createRegion(JNIEnv* env, SkRegion* region);
static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d26b5a2..106f4bd 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -85,26 +85,20 @@
}
/**
- * @noinspection UnusedDeclaration
+ * Private constructor that must received an already allocated native
+ * bitmap int (pointer).
*/
- /* Private constructor that must received an already allocated native
- bitmap int (pointer).
-
- This can be called from JNI code.
- */
+ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
int density) {
this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density);
}
/**
- * @noinspection UnusedDeclaration
+ * Private constructor that must received an already allocated native bitmap
+ * int (pointer).
*/
- /* Private constructor that must received an already allocated native
- bitmap int (pointer).
-
- This can be called from JNI code.
- */
+ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk,
int[] layoutBounds, int density) {
if (nativeBitmap == 0) {
@@ -125,6 +119,14 @@
}
/**
+ * Native bitmap has been reconfigured, so discard cached width/height
+ */
+ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI
+ void reinit() {
+ mWidth = mHeight = -1;
+ }
+
+ /**
* <p>Returns the density for this bitmap.</p>
*
* <p>The default density is the same density as the current display,
@@ -454,7 +456,7 @@
/**
* Creates a new bitmap, scaled from an existing bitmap, when possible. If the
* specified width and height are the same as the current width and height of
- * the source btimap, the source bitmap is returned and now new bitmap is
+ * the source bitmap, the source bitmap is returned and no new bitmap is
* created.
*
* @param src The source bitmap.
@@ -989,6 +991,10 @@
* getPixels() or setPixels(), then the pixels are uniformly treated as
* 32bit values, packed according to the Color class.
*
+ * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method
+ * should not be used to calculate the memory usage of the bitmap. Instead,
+ * see {@link #getAllocationByteCount()}.
+ *
* @return number of bytes between rows of the native bitmap pixels.
*/
public final int getRowBytes() {
@@ -996,7 +1002,11 @@
}
/**
- * Returns the number of bytes used to store this bitmap's pixels.
+ * Returns the minimum number of bytes that can be used to store this bitmap's pixels.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, the result of this method can
+ * no longer be used to determine memory usage of a bitmap. See {@link
+ * #getAllocationByteCount()}.
*/
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
@@ -1004,6 +1014,20 @@
}
/**
+ * Returns the size of the allocated memory used to store this bitmap's pixels.
+ *
+ * <p>This can be larger than the result of {@link #getByteCount()} if a bitmap is reused to
+ * decode other bitmaps of smaller size. See {@link BitmapFactory.Options#inBitmap inBitmap in
+ * BitmapFactory.Options}. If a bitmap is not reused in this way, this value will be the same as
+ * that returned by {@link #getByteCount()}.
+ *
+ * <p>This value will not change over the lifetime of a Bitmap.
+ */
+ public final int getAllocationByteCount() {
+ return mBuffer.length;
+ }
+
+ /**
* If the bitmap's internal config is in one of the public formats, return
* that config, otherwise return null.
*/
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index f155cd2..78ee7c6 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -50,13 +50,22 @@
* reuse this bitmap when loading content. If the decode operation cannot
* use this bitmap, the decode method will return <code>null</code> and
* will throw an IllegalArgumentException. The current implementation
- * necessitates that the reused bitmap be mutable and of the same size as the
- * source content. The source content must be in jpeg or png format (whether as
- * a resource or as a stream). The {@link android.graphics.Bitmap.Config
- * configuration} of the reused bitmap will override the setting of
- * {@link #inPreferredConfig}, if set. The reused bitmap will continue to
- * remain mutable even when decoding a resource which would normally result
- * in an immutable bitmap.
+ * necessitates that the reused bitmap be mutable and of a size that is
+ * equal to or larger than the source content. The source content must be
+ * in jpeg or png format (whether as a resource or as a stream). The
+ * {@link android.graphics.Bitmap.Config configuration} of the reused
+ * bitmap will override the setting of {@link #inPreferredConfig}, if set.
+ * The reused bitmap will continue to remain mutable even when decoding a
+ * resource which would normally result in an immutable bitmap.
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, the reused
+ * bitmap can be used to decode any other bitmaps as long as the resulting
+ * {@link Bitmap#getByteCount() byte count} of the decoded bitmap is less
+ * than or equal to the {@link Bitmap#getAllocationByteCount() allocated byte count}
+ * of the reused bitmap. This can be because the intrinsic size is smaller,
+ * or the sampled size is smaller. Prior to
+ * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, only equal sized
+ * bitmaps are supported, with {@link #inSampleSize} set to 1.
*
* <p>You should still always use the returned Bitmap of the decode
* method and not assume that reusing the bitmap worked, due to the