Loading core/jni/android/graphics/BitmapFactory.cpp +87 −61 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -152,6 +156,43 @@ 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) Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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"); Loading Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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, Loading Loading @@ -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;", Loading @@ -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 Loading Loading @@ -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;"); Loading graphics/java/android/graphics/BitmapFactory.java +46 −103 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -456,6 +458,31 @@ public class BitmapFactory { return decodeByteArray(data, offset, length, null); } /** * 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. Loading Loading @@ -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); } } 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 Loading @@ -523,79 +532,15 @@ 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 { 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; } Loading Loading @@ -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 { Loading @@ -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); Loading Loading
core/jni/android/graphics/BitmapFactory.cpp +87 −61 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -152,6 +156,43 @@ 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) Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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"); Loading Loading @@ -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); Loading @@ -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"); Loading Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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, Loading Loading @@ -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;", Loading @@ -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 Loading Loading @@ -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;"); Loading
graphics/java/android/graphics/BitmapFactory.java +46 −103 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -456,6 +458,31 @@ public class BitmapFactory { return decodeByteArray(data, offset, length, null); } /** * 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. Loading Loading @@ -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); } } 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 Loading @@ -523,79 +532,15 @@ 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 { 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; } Loading Loading @@ -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 { Loading @@ -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); Loading