Merge "Add support for post-decode density scaling with reuse"
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 38dee1b..a7a9266 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -29,6 +29,10 @@
jfieldID gOptions_purgeableFieldID;
jfieldID gOptions_shareableFieldID;
jfieldID gOptions_preferQualityOverSpeedFieldID;
+jfieldID gOptions_scaledFieldID;
+jfieldID gOptions_densityFieldID;
+jfieldID gOptions_screenDensityFieldID;
+jfieldID gOptions_targetDensityFieldID;
jfieldID gOptions_widthFieldID;
jfieldID gOptions_heightFieldID;
jfieldID gOptions_mimeFieldID;
@@ -152,10 +156,47 @@
return pr;
}
+static SkBitmap::Config configForScaledOutput(SkBitmap::Config config) {
+ switch (config) {
+ case SkBitmap::kNo_Config:
+ case SkBitmap::kIndex8_Config:
+ case SkBitmap::kRLE_Index8_Config:
+ return SkBitmap::kARGB_8888_Config;
+ default:
+ break;
+ }
+ return config;
+}
+
+class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
+public:
+ ScaleCheckingAllocator(float scale, int size)
+ : mScale(scale), mSize(size) {
+ }
+
+ virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
+ // accounts for scale in final allocation, using eventual size and config
+ const int bytesPerPixel = SkBitmap::ComputeBytesPerPixel(
+ configForScaledOutput(bitmap->getConfig()));
+ const int requestedSize = bytesPerPixel *
+ int(bitmap->width() * mScale + 0.5f) *
+ int(bitmap->height() * mScale + 0.5f);
+ if (requestedSize > mSize) {
+ ALOGW("bitmap for alloc reuse (%d bytes) can't fit scaled bitmap (%d bytes)",
+ mSize, requestedSize);
+ return false;
+ }
+ return SkBitmap::HeapAllocator::allocPixelRef(bitmap, ctable);
+ }
+private:
+ const float mScale;
+ const int mSize;
+};
+
class RecyclingPixelAllocator : public SkBitmap::Allocator {
public:
RecyclingPixelAllocator(SkPixelRef* pixelRef, unsigned int size)
- : mPixelRef(pixelRef), mSize(size) {
+ : mPixelRef(pixelRef), mSize(size) {
SkSafeRef(mPixelRef);
}
@@ -165,8 +206,8 @@
virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
if (!bitmap->getSize64().is32() || bitmap->getSize() > mSize) {
- ALOGW("bitmap marked for reuse (%d bytes) too small to contain new bitmap (%d bytes)",
- bitmap->getSize(), mSize);
+ ALOGW("bitmap marked for reuse (%d bytes) can't fit new bitmap (%d bytes)",
+ mSize, bitmap->getSize());
return false;
}
bitmap->setPixelRef(mPixelRef);
@@ -183,8 +224,7 @@
// i.e. dynamically allocated, since its lifetime may exceed the current stack
// frame.
static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
- jobject options, bool allowPurgeable, bool forcePurgeable = false,
- bool applyScale = false, float scale = 1.0f) {
+ jobject options, bool allowPurgeable, bool forcePurgeable = false) {
int sampleSize = 1;
@@ -193,9 +233,8 @@
bool doDither = true;
bool isMutable = false;
- bool willScale = applyScale && scale != 1.0f;
- bool isPurgeable = !willScale &&
- (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
+ float scale = 1.0f;
+ bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
bool preferQualityOverSpeed = false;
jobject javaBitmap = NULL;
@@ -218,12 +257,19 @@
preferQualityOverSpeed = env->GetBooleanField(options,
gOptions_preferQualityOverSpeedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
+
+ if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
+ const int density = env->GetIntField(options, gOptions_densityFieldID);
+ const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
+ const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
+ if (density != 0 && targetDensity != 0 && density != screenDensity) {
+ scale = (float) targetDensity / density;
+ }
+ }
}
- // TODO: allow scaling with reuse, ideally avoiding decode in not-enough-space condition
- if (willScale && javaBitmap != NULL) {
- return nullObjectReturn("Cannot pre-scale a reused bitmap");
- }
+ const bool willScale = scale != 1.0f;
+ isPurgeable &= !willScale;
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
if (decoder == NULL) {
@@ -254,16 +300,6 @@
NinePatchPeeker peeker(decoder);
decoder->setPeeker(&peeker);
- JavaPixelAllocator javaAllocator(env);
- RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
-
- // allocator to be used for final allocation associated with output
- SkBitmap::Allocator* allocator = (javaBitmap != NULL) ?
- (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;
-
- if (!isPurgeable && !willScale) {
- decoder->setAllocator(allocator);
- }
AutoDecoderCancel adc(options, decoder);
@@ -276,6 +312,22 @@
SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;
+
+ JavaPixelAllocator javaAllocator(env);
+ RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
+ ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
+ SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ?
+ (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;
+ if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
+ if (!willScale) {
+ decoder->setAllocator(outputAllocator);
+ } else if (javaBitmap != NULL) {
+ // check for eventual scaled bounds at allocation time, so we don't decode the bitmap
+ // only to find the scaled result too large to fit in the allocation
+ decoder->setAllocator(&scaleCheckingAllocator);
+ }
+ }
+
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) {
return nullObjectReturn("decoder->decode returned false");
@@ -353,20 +405,11 @@
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
- SkBitmap::Config config = decodingBitmap.config();
- switch (config) {
- case SkBitmap::kNo_Config:
- case SkBitmap::kIndex8_Config:
- case SkBitmap::kRLE_Index8_Config:
- config = SkBitmap::kARGB_8888_Config;
- break;
- default:
- break;
- }
-
+ // TODO: avoid copying when scaled size equals decodingBitmap size
+ SkBitmap::Config config = configForScaledOutput(decodingBitmap.config());
outputBitmap->setConfig(config, scaledWidth, scaledHeight);
outputBitmap->setIsOpaque(decodingBitmap.isOpaque());
- if (!outputBitmap->allocPixels(allocator, NULL)) {
+ if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
return nullObjectReturn("allocation failed for scaled bitmap");
}
outputBitmap->eraseColor(0);
@@ -422,26 +465,20 @@
isMutable, ninePatchChunk, layoutBounds, -1);
}
-static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
- jobject padding, jobject options, jboolean applyScale, jfloat scale) {
+static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
+ jobject padding, jobject options) {
jobject bitmap = NULL;
SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
if (stream) {
// for now we don't allow purgeable with java inputstreams
- bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale);
+ bitmap = doDecode(env, stream, padding, options, false, false);
stream->unref();
}
return bitmap;
}
-static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
- jobject padding, jobject options) {
-
- return nativeDecodeStreamScaled(env, clazz, is, storage, padding, options, false, 1.0f);
-}
-
static ssize_t getFDSize(int fd) {
off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
if (curr < 0) {
@@ -516,8 +553,8 @@
return stream;
}
-static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset,
- jobject padding, jobject options, jboolean applyScale, jfloat scale) {
+static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
+ jobject padding, jobject options) {
SkStream* stream;
Asset* asset = reinterpret_cast<Asset*>(native_asset);
@@ -535,13 +572,7 @@
stream = new AssetStreamAdaptor(asset);
}
SkAutoUnref aur(stream);
- return doDecode(env, stream, padding, options, true, forcePurgeable, applyScale, scale);
-}
-
-static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
- jobject padding, jobject options) {
-
- return nativeDecodeAssetScaled(env, clazz, native_asset, padding, options, false, 1.0f);
+ return doDecode(env, stream, padding, options, true, forcePurgeable);
}
static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -575,10 +606,6 @@
"(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
(void*)nativeDecodeStream
},
- { "nativeDecodeStream",
- "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
- (void*)nativeDecodeStreamScaled
- },
{ "nativeDecodeFileDescriptor",
"(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
@@ -590,11 +617,6 @@
(void*)nativeDecodeAsset
},
- { "nativeDecodeAsset",
- "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
- (void*)nativeDecodeAssetScaled
- },
-
{ "nativeDecodeByteArray",
"([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
(void*)nativeDecodeByteArray
@@ -637,6 +659,10 @@
gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
"inPreferQualityOverSpeed", "Z");
+ gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z");
+ gOptions_densityFieldID = getFieldIDCheck(env, options_class, "inDensity", "I");
+ gOptions_screenDensityFieldID = getFieldIDCheck(env, options_class, "inScreenDensity", "I");
+ gOptions_targetDensityFieldID = getFieldIDCheck(env, options_class, "inTargetDensity", "I");
gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index b89d042..deccac1 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -50,29 +50,30 @@
* 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 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
+ * necessitates that the reused bitmap be mutable, and the resulting
+ * 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
+ * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, any mutable
+ * bitmap can be reused 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.
+ * or the size after density / sampled size scaling is smaller.
+ *
+ * <p>Prior to {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE} additional
+ * constraints apply: The image being decoded (whether as a resource or
+ * as a stream) must be in jpeg or png format. Only equal sized bitmaps
+ * are supported, with {@link #inSampleSize} set to 1. Additionally, the
+ * {@link android.graphics.Bitmap.Config configuration} of the reused
+ * bitmap will override the setting of {@link #inPreferredConfig}, if set.
*
* <p>You should still always use the returned Bitmap of the decode
* method and not assume that reusing the bitmap worked, due to the
* constraints outlined above and failure situations that can occur.
* Checking whether the return value matches the value of the inBitmap
- * set in the Options structure is a way to see if the bitmap was reused,
- * but in all cases you should use the returned Bitmap to make sure
+ * set in the Options structure will indicate if the bitmap was reused,
+ * but in all cases you should use the Bitmap returned by the decoding function to ensure
* that you are using the bitmap that was used as the decode destination.</p>
*/
public Bitmap inBitmap;
@@ -440,6 +441,7 @@
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
+ setDensityFromOptions(bm, opts);
return bm;
}
@@ -457,6 +459,31 @@
}
/**
+ * Set the newly decoded bitmap's density based on the Options.
+ */
+ private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
+ if (outputBitmap == null || opts == null) return;
+
+ final int density = opts.inDensity;
+ if (density != 0) {
+ outputBitmap.setDensity(density);
+ final int targetDensity = opts.inTargetDensity;
+ if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
+ return;
+ }
+
+ byte[] np = outputBitmap.getNinePatchChunk();
+ final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
+ if (opts.inScaled || isNinePatch) {
+ outputBitmap.setDensity(targetDensity);
+ }
+ } else if (opts.inBitmap != null) {
+ // bitmap was reused, ensure density is reset
+ outputBitmap.setDensity(Bitmap.getDefaultDensity());
+ }
+ }
+
+ /**
* Decode an input stream into a bitmap. If the input stream is null, or
* cannot be used to decode a bitmap, the function returns null.
* The stream's position will be where ever it was after the encoded data
@@ -497,25 +524,7 @@
if (is instanceof AssetManager.AssetInputStream) {
final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
-
- if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
- float scale = 1.0f;
- int targetDensity = 0;
- if (opts != null) {
- final int density = opts.inDensity;
- targetDensity = opts.inTargetDensity;
- if (density != 0 && targetDensity != 0) {
- scale = targetDensity / (float) density;
- }
- }
-
- bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
- if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
-
- finish = false;
- } else {
- bm = nativeDecodeAsset(asset, outPadding, opts);
- }
+ bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
@@ -523,82 +532,18 @@
// to mark(...) above.
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
- if (tempStorage == null) tempStorage = new byte[16 * 1024];
-
- if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
- float scale = 1.0f;
- int targetDensity = 0;
- if (opts != null) {
- final int density = opts.inDensity;
- targetDensity = opts.inTargetDensity;
- if (density != 0 && targetDensity != 0) {
- scale = targetDensity / (float) density;
- }
- }
-
- bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale);
- if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
-
- finish = false;
- } else {
- bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
- }
+ if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
+ bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
- return finish ? finishDecode(bm, outPadding, opts) : bm;
- }
-
- // TODO: remove this path, implement any needed functionality in native decode
- private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
- if (bm == null || opts == null) {
- return bm;
- }
-
- final int density = opts.inDensity;
- if (density == 0) {
- return bm;
- }
-
- bm.setDensity(density);
- final int targetDensity = opts.inTargetDensity;
- if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
- return bm;
- }
- byte[] np = bm.getNinePatchChunk();
- int[] lb = bm.getLayoutBounds();
- final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
- if (opts.inScaled || isNinePatch) {
- float scale = targetDensity / (float) density;
- if (scale != 1.0f) {
- final Bitmap oldBitmap = bm;
- bm = Bitmap.createScaledBitmap(oldBitmap,
- Math.max(1, (int) (bm.getWidth() * scale + 0.5f)),
- Math.max(1, (int) (bm.getHeight() * scale + 0.5f)), true);
- if (bm != oldBitmap) oldBitmap.recycle();
-
- if (isNinePatch) {
- np = nativeScaleNinePatch(np, scale, outPadding);
- bm.setNinePatchChunk(np);
- }
- if (lb != null) {
- int[] newLb = new int[lb.length];
- for (int i=0; i<lb.length; i++) {
- newLb[i] = (int)((lb[i]*scale)+.5f);
- }
- bm.setLayoutBounds(newLb);
- }
- }
-
- bm.setDensity(targetDensity);
- }
-
+ setDensityFromOptions(bm, opts);
return bm;
}
-
+
/**
* Decode an input stream into a bitmap. If the input stream is null, or
* cannot be used to decode a bitmap, the function returns null.
@@ -633,7 +578,7 @@
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
- return finishDecode(bm, outPadding, opts);
+ return bm;
} else {
FileInputStream fis = new FileInputStream(fd);
try {
@@ -660,8 +605,6 @@
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
Rect padding, Options opts);
- private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- Rect padding, Options opts, boolean applyScale, float scale);
private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
Rect padding, Options opts);
private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts);