diff options
| author | 2013-06-06 21:04:48 +0000 | |
|---|---|---|
| committer | 2013-06-06 21:04:49 +0000 | |
| commit | 34bff87b32a10f6267e76a7de0b287eb6a4633f9 (patch) | |
| tree | 5a2f1976d69be51868c5f2311258a06f94140699 | |
| parent | c840a13342d02a6197f79396349885f1cf7c437c (diff) | |
| parent | 905e8246ef0bd20ee28d81ce3da0c5e5fc8e3913 (diff) | |
Merge "Add support for post-decode density scaling with reuse"
| -rw-r--r-- | core/jni/android/graphics/BitmapFactory.cpp | 148 | ||||
| -rw-r--r-- | graphics/java/android/graphics/BitmapFactory.java | 149 |
2 files changed, 133 insertions, 164 deletions
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 38dee1b9be68..a7a9266bf53c 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -29,6 +29,10 @@ jfieldID gOptions_ditherFieldID; 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 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, 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 @@ public: 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 @@ private: // 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 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 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,13 +257,20 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); - } - // 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"); + 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; + } + } } + const bool willScale = scale != 1.0f; + isPurgeable &= !willScale; + SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (decoder == NULL) { return nullObjectReturn("SkImageDecoder::Factory returned null"); @@ -254,16 +300,6 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 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 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 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 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 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 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 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 @@ static SkStream* copyAssetToStream(Asset* asset) { 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 @@ static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_a 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 @@ static JNINativeMethod gMethods[] = { "(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 @@ static JNINativeMethod gMethods[] = { (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 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { 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 b89d0425353d..deccac1aa01a 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -50,29 +50,30 @@ public class BitmapFactory { * 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 @@ public class BitmapFactory { 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 @@ public class BitmapFactory { } /** + * 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 @@ public class BitmapFactory { 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 @@ public class BitmapFactory { // 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 @@ public class BitmapFactory { 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 @@ public class BitmapFactory { 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); |