Loading core/java/android/view/ViewRootImpl.java +3 −1 Original line number Diff line number Diff line Loading @@ -841,7 +841,9 @@ public final class ViewRootImpl implements ViewParent, localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region localDirty.intersect(0, 0, mWidth, mHeight); final float appScale = mAttachInfo.mApplicationScale; localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!mWillDrawSoon) { scheduleTraversals(); Loading core/jni/android/graphics/BitmapFactory.cpp +163 −93 Original line number Diff line number Diff line Loading @@ -90,8 +90,54 @@ static bool optionsJustBounds(JNIEnv* env, jobject options) { return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID); } static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f); chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f); chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f); chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f); for (int i = 0; i < chunk->numXDivs; i++) { chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f); if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) { chunk->xDivs[i]++; } } for (int i = 0; i < chunk->numYDivs; i++) { chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f); if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) { chunk->yDivs[i]++; } } } static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale, jobject padding) { jbyte* array = env->GetByteArrayElements(chunkObject, 0); if (array != NULL) { size_t chunkSize = env->GetArrayLength(chunkObject); void* storage = alloca(chunkSize); android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage); memcpy(chunk, array, chunkSize); android::Res_png_9patch::deserialize(chunk); scaleNinePatchChunk(chunk, scale); memcpy(array, chunk, chunkSize); if (padding) { GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop, chunk->paddingRight, chunk->paddingBottom); } env->ReleaseByteArrayElements(chunkObject, array, 0); } return chunkObject; } static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, int sampleSize, bool ditherImage) { SkImageRef* pr; // only use ashmem for large images, since mmaps come at a price if (bitmap->getSize() >= 32 * 1024) { Loading @@ -109,22 +155,29 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, // 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) { jobject options, bool allowPurgeable, bool forcePurgeable = false, bool applyScale = false, float scale = 1.0f) { int sampleSize = 1; SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; bool doDither = true; bool isMutable = false; bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); bool willScale = applyScale && scale != 1.0f; bool isPurgeable = !willScale && (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options))); bool preferQualityOverSpeed = false; jobject javaBitmap = NULL; if (NULL != options) { if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { mode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); Loading @@ -139,8 +192,12 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); } if (willScale && javaBitmap != NULL) { return nullObjectReturn("Cannot pre-scale a reused bitmap"); } SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (NULL == decoder) { if (decoder == NULL) { return nullObjectReturn("SkImageDecoder::Factory returned null"); } Loading @@ -150,6 +207,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, NinePatchPeeker peeker(decoder); JavaPixelAllocator javaAllocator(env); SkBitmap* bitmap; if (javaBitmap == NULL) { bitmap = new SkBitmap; Loading @@ -161,10 +219,9 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // config of supplied bitmap overrules config set in options prefConfig = bitmap->getConfig(); } Res_png_9patch dummy9Patch; SkAutoTDelete<SkImageDecoder> add(decoder); SkAutoTDelete<SkBitmap> adb(bitmap, (javaBitmap == NULL)); SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL); decoder->setPeeker(&peeker); if (!isPurgeable) { Loading @@ -176,7 +233,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID"); } Loading @@ -184,38 +241,57 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, if (isPurgeable) { decodeMode = SkImageDecoder::kDecodeBounds_Mode; } if (!decoder->decode(stream, bitmap, prefConfig, decodeMode, javaBitmap != NULL)) { SkBitmap* decoded; if (willScale) { decoded = new SkBitmap; } else { decoded = bitmap; } SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL); if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) { return nullObjectReturn("decoder->decode returned false"); } int scaledWidth = decoded->width(); int scaledHeight = decoded->height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } // update options (if any) if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? if (options != NULL) { env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } // if we're in justBounds mode, return now (skip the java bitmap) if (SkImageDecoder::kDecodeBounds_Mode == mode) { if (mode == SkImageDecoder::kDecodeBounds_Mode) { return NULL; } jbyteArray ninePatchChunk = NULL; if (peeker.fPatchIsValid) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } size_t ninePatchArraySize = peeker.fPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (NULL == ninePatchChunk) { if (ninePatchChunk == NULL) { return nullObjectReturn("ninePatchChunk == null"); } jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (NULL == array) { jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (array == NULL) { return nullObjectReturn("primitive array == null"); } peeker.fPatch->serialize(array); env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } Loading @@ -223,13 +299,32 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // detach bitmap from its autodeleter, since we want to own it now adb.detach(); if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size const float sx = scaledWidth / float(decoded->width()); const float sy = scaledHeight / float(decoded->height()); bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight); bitmap->allocPixels(&javaAllocator, NULL); bitmap->eraseColor(0); SkPaint paint; paint.setFilterBitmap(true); SkCanvas canvas(*bitmap); canvas.scale(sx, sy); canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint); } if (padding) { if (peeker.fPatchIsValid) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); } else { GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1); } Loading Loading @@ -258,22 +353,26 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, isMutable, ninePatchChunk); } static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, // InputStream jbyteArray storage, // byte[] jobject padding, jobject options) { // BitmapFactory$Options static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options, jboolean applyScale, jfloat scale) { 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); bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale); 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 @@ -284,10 +383,9 @@ static ssize_t getFDSize(int fd) { return size; } static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions) { static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions) { NPE_CHECK_RETURN_ZERO(env, fileDescriptor); jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); Loading Loading @@ -349,10 +447,9 @@ static SkStream* copyAssetToStream(Asset* asset) { return stream; } static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, // Asset jobject padding, // Rect jobject options) { // BitmapFactory$Options static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset, jobject padding, jobject options, jboolean applyScale, jfloat scale) { SkStream* stream; Asset* asset = reinterpret_cast<Asset*>(native_asset); bool forcePurgeable = optionsPurgeable(env, options); Loading @@ -360,7 +457,7 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, // if we could "ref/reopen" the asset, we may not need to copy it here // and we could assume optionsShareable, since assets are always RO stream = copyAssetToStream(asset); if (NULL == stream) { if (stream == NULL) { return NULL; } } else { Loading @@ -369,18 +466,24 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, stream = new AssetStreamAdaptor(asset); } SkAutoUnref aur(stream); return doDecode(env, stream, padding, options, true, forcePurgeable); 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); } static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, int offset, int length, jobject options) { /* If optionsShareable() we could decide to just wrap the java array and share it, but that means adding a globalref to the java array object and managing its lifetime. For now we just always copy the array's data if optionsPurgeable(), unless we're just decoding bounds. */ bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); AutoJavaByteArray ar(env, byteArray); SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); SkAutoUnref aur(stream); Loading @@ -391,48 +494,6 @@ static void nativeRequestCancel(JNIEnv*, jobject joptions) { (void)AutoDecoderCancel::RequestCancel(joptions); } static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale, jobject padding) { jbyte* array = env->GetByteArrayElements(chunkObject, 0); if (array != NULL) { size_t chunkSize = env->GetArrayLength(chunkObject); void* storage = alloca(chunkSize); android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage); memcpy(chunk, array, chunkSize); android::Res_png_9patch::deserialize(chunk); chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f); chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f); chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f); chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f); for (int i = 0; i < chunk->numXDivs; i++) { chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f); if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) { chunk->xDivs[i]++; } } for (int i = 0; i < chunk->numYDivs; i++) { chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f); if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) { chunk->yDivs[i]++; } } memcpy(array, chunk, chunkSize); if (padding) { GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop, chunk->paddingRight, chunk->paddingBottom); } env->ReleaseByteArrayElements(chunkObject, array, 0); } return chunkObject; } static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE; Loading @@ -445,6 +506,10 @@ 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 @@ -456,6 +521,11 @@ 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 graphics/java/android/graphics/BitmapFactory.java +55 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import java.io.InputStream; * and byte-arrays. */ public class BitmapFactory { private static final int DECODE_BUFFER_SIZE = 16 * 1024; public static class Options { /** * Create a default Options object, which if left unchanged will give Loading Loading @@ -469,7 +471,7 @@ public class BitmapFactory { // we need mark/reset to work properly if (!is.markSupported()) { is = new BufferedInputStream(is, 16 * 1024); is = new BufferedInputStream(is, DECODE_BUFFER_SIZE); } // so we can call reset() if a given codec gives up after reading up to Loading @@ -478,10 +480,29 @@ public class BitmapFactory { is.mark(1024); Bitmap bm; boolean finish = true; if (is instanceof AssetManager.AssetInputStream) { bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(), outPadding, opts); 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 @@ -490,13 +511,32 @@ public class BitmapFactory { 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 (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } return finishDecode(bm, outPadding, opts); return finish ? finishDecode(bm, outPadding, opts) : bm; } private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { Loading Loading @@ -524,12 +564,13 @@ public class BitmapFactory { bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), (int) (bm.getHeight() * scale + 0.5f), true); if (bm != oldBitmap) oldBitmap.recycle(); } if (isNinePatch) { if (scale != 1.0f) np = nativeScaleNinePatch(np, scale, outPadding); np = nativeScaleNinePatch(np, scale, outPadding); bm.setNinePatchChunk(np); } } bm.setDensity(targetDensity); } Loading Loading @@ -597,9 +638,13 @@ 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); private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts, boolean applyScale, float scale); private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); private static native byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad); Loading Loading
core/java/android/view/ViewRootImpl.java +3 −1 Original line number Diff line number Diff line Loading @@ -841,7 +841,9 @@ public final class ViewRootImpl implements ViewParent, localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region localDirty.intersect(0, 0, mWidth, mHeight); final float appScale = mAttachInfo.mApplicationScale; localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!mWillDrawSoon) { scheduleTraversals(); Loading
core/jni/android/graphics/BitmapFactory.cpp +163 −93 Original line number Diff line number Diff line Loading @@ -90,8 +90,54 @@ static bool optionsJustBounds(JNIEnv* env, jobject options) { return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID); } static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f); chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f); chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f); chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f); for (int i = 0; i < chunk->numXDivs; i++) { chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f); if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) { chunk->xDivs[i]++; } } for (int i = 0; i < chunk->numYDivs; i++) { chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f); if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) { chunk->yDivs[i]++; } } } static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale, jobject padding) { jbyte* array = env->GetByteArrayElements(chunkObject, 0); if (array != NULL) { size_t chunkSize = env->GetArrayLength(chunkObject); void* storage = alloca(chunkSize); android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage); memcpy(chunk, array, chunkSize); android::Res_png_9patch::deserialize(chunk); scaleNinePatchChunk(chunk, scale); memcpy(array, chunk, chunkSize); if (padding) { GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop, chunk->paddingRight, chunk->paddingBottom); } env->ReleaseByteArrayElements(chunkObject, array, 0); } return chunkObject; } static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, int sampleSize, bool ditherImage) { SkImageRef* pr; // only use ashmem for large images, since mmaps come at a price if (bitmap->getSize() >= 32 * 1024) { Loading @@ -109,22 +155,29 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, // 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) { jobject options, bool allowPurgeable, bool forcePurgeable = false, bool applyScale = false, float scale = 1.0f) { int sampleSize = 1; SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; bool doDither = true; bool isMutable = false; bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); bool willScale = applyScale && scale != 1.0f; bool isPurgeable = !willScale && (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options))); bool preferQualityOverSpeed = false; jobject javaBitmap = NULL; if (NULL != options) { if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { mode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); Loading @@ -139,8 +192,12 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); } if (willScale && javaBitmap != NULL) { return nullObjectReturn("Cannot pre-scale a reused bitmap"); } SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (NULL == decoder) { if (decoder == NULL) { return nullObjectReturn("SkImageDecoder::Factory returned null"); } Loading @@ -150,6 +207,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, NinePatchPeeker peeker(decoder); JavaPixelAllocator javaAllocator(env); SkBitmap* bitmap; if (javaBitmap == NULL) { bitmap = new SkBitmap; Loading @@ -161,10 +219,9 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // config of supplied bitmap overrules config set in options prefConfig = bitmap->getConfig(); } Res_png_9patch dummy9Patch; SkAutoTDelete<SkImageDecoder> add(decoder); SkAutoTDelete<SkBitmap> adb(bitmap, (javaBitmap == NULL)); SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL); decoder->setPeeker(&peeker); if (!isPurgeable) { Loading @@ -176,7 +233,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID"); } Loading @@ -184,38 +241,57 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, if (isPurgeable) { decodeMode = SkImageDecoder::kDecodeBounds_Mode; } if (!decoder->decode(stream, bitmap, prefConfig, decodeMode, javaBitmap != NULL)) { SkBitmap* decoded; if (willScale) { decoded = new SkBitmap; } else { decoded = bitmap; } SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL); if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) { return nullObjectReturn("decoder->decode returned false"); } int scaledWidth = decoded->width(); int scaledHeight = decoded->height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } // update options (if any) if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? if (options != NULL) { env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } // if we're in justBounds mode, return now (skip the java bitmap) if (SkImageDecoder::kDecodeBounds_Mode == mode) { if (mode == SkImageDecoder::kDecodeBounds_Mode) { return NULL; } jbyteArray ninePatchChunk = NULL; if (peeker.fPatchIsValid) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } size_t ninePatchArraySize = peeker.fPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (NULL == ninePatchChunk) { if (ninePatchChunk == NULL) { return nullObjectReturn("ninePatchChunk == null"); } jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (NULL == array) { jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (array == NULL) { return nullObjectReturn("primitive array == null"); } peeker.fPatch->serialize(array); env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } Loading @@ -223,13 +299,32 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, // detach bitmap from its autodeleter, since we want to own it now adb.detach(); if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size const float sx = scaledWidth / float(decoded->width()); const float sy = scaledHeight / float(decoded->height()); bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight); bitmap->allocPixels(&javaAllocator, NULL); bitmap->eraseColor(0); SkPaint paint; paint.setFilterBitmap(true); SkCanvas canvas(*bitmap); canvas.scale(sx, sy); canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint); } if (padding) { if (peeker.fPatchIsValid) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); } else { GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1); } Loading Loading @@ -258,22 +353,26 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, isMutable, ninePatchChunk); } static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, // InputStream jbyteArray storage, // byte[] jobject padding, jobject options) { // BitmapFactory$Options static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options, jboolean applyScale, jfloat scale) { 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); bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale); 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 @@ -284,10 +383,9 @@ static ssize_t getFDSize(int fd) { return size; } static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions) { static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions) { NPE_CHECK_RETURN_ZERO(env, fileDescriptor); jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); Loading Loading @@ -349,10 +447,9 @@ static SkStream* copyAssetToStream(Asset* asset) { return stream; } static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, // Asset jobject padding, // Rect jobject options) { // BitmapFactory$Options static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset, jobject padding, jobject options, jboolean applyScale, jfloat scale) { SkStream* stream; Asset* asset = reinterpret_cast<Asset*>(native_asset); bool forcePurgeable = optionsPurgeable(env, options); Loading @@ -360,7 +457,7 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, // if we could "ref/reopen" the asset, we may not need to copy it here // and we could assume optionsShareable, since assets are always RO stream = copyAssetToStream(asset); if (NULL == stream) { if (stream == NULL) { return NULL; } } else { Loading @@ -369,18 +466,24 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, stream = new AssetStreamAdaptor(asset); } SkAutoUnref aur(stream); return doDecode(env, stream, padding, options, true, forcePurgeable); 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); } static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, int offset, int length, jobject options) { /* If optionsShareable() we could decide to just wrap the java array and share it, but that means adding a globalref to the java array object and managing its lifetime. For now we just always copy the array's data if optionsPurgeable(), unless we're just decoding bounds. */ bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); AutoJavaByteArray ar(env, byteArray); SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); SkAutoUnref aur(stream); Loading @@ -391,48 +494,6 @@ static void nativeRequestCancel(JNIEnv*, jobject joptions) { (void)AutoDecoderCancel::RequestCancel(joptions); } static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale, jobject padding) { jbyte* array = env->GetByteArrayElements(chunkObject, 0); if (array != NULL) { size_t chunkSize = env->GetArrayLength(chunkObject); void* storage = alloca(chunkSize); android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage); memcpy(chunk, array, chunkSize); android::Res_png_9patch::deserialize(chunk); chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f); chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f); chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f); chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f); for (int i = 0; i < chunk->numXDivs; i++) { chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f); if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) { chunk->xDivs[i]++; } } for (int i = 0; i < chunk->numYDivs; i++) { chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f); if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) { chunk->yDivs[i]++; } } memcpy(array, chunk, chunkSize); if (padding) { GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop, chunk->paddingRight, chunk->paddingBottom); } env->ReleaseByteArrayElements(chunkObject, array, 0); } return chunkObject; } static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE; Loading @@ -445,6 +506,10 @@ 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 @@ -456,6 +521,11 @@ 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
graphics/java/android/graphics/BitmapFactory.java +55 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import java.io.InputStream; * and byte-arrays. */ public class BitmapFactory { private static final int DECODE_BUFFER_SIZE = 16 * 1024; public static class Options { /** * Create a default Options object, which if left unchanged will give Loading Loading @@ -469,7 +471,7 @@ public class BitmapFactory { // we need mark/reset to work properly if (!is.markSupported()) { is = new BufferedInputStream(is, 16 * 1024); is = new BufferedInputStream(is, DECODE_BUFFER_SIZE); } // so we can call reset() if a given codec gives up after reading up to Loading @@ -478,10 +480,29 @@ public class BitmapFactory { is.mark(1024); Bitmap bm; boolean finish = true; if (is instanceof AssetManager.AssetInputStream) { bm = nativeDecodeAsset(((AssetManager.AssetInputStream) is).getAssetInt(), outPadding, opts); 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 @@ -490,13 +511,32 @@ public class BitmapFactory { 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 (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } return finishDecode(bm, outPadding, opts); return finish ? finishDecode(bm, outPadding, opts) : bm; } private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { Loading Loading @@ -524,12 +564,13 @@ public class BitmapFactory { bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), (int) (bm.getHeight() * scale + 0.5f), true); if (bm != oldBitmap) oldBitmap.recycle(); } if (isNinePatch) { if (scale != 1.0f) np = nativeScaleNinePatch(np, scale, outPadding); np = nativeScaleNinePatch(np, scale, outPadding); bm.setNinePatchChunk(np); } } bm.setDensity(targetDensity); } Loading Loading @@ -597,9 +638,13 @@ 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); private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts, boolean applyScale, float scale); private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); private static native byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad); Loading