diff options
author | Romain Guy <romainguy@google.com> | 2012-03-19 18:52:09 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-03-19 18:52:09 -0700 |
commit | 00843227504528584596da305e6587450c0c5223 (patch) | |
tree | 85b310040e7ff0dcf6227f4631960d5f59754db9 | |
parent | f1bdf65308af0ced783ffffd60b841a6bc0da970 (diff) | |
parent | 7b2f8b8fb7064a1d3b6d942b978c30c24c9d7299 (diff) | |
download | frameworks_base-00843227504528584596da305e6587450c0c5223.zip frameworks_base-00843227504528584596da305e6587450c0c5223.tar.gz frameworks_base-00843227504528584596da305e6587450c0c5223.tar.bz2 |
Merge "Pre-scale bitmaps on the native heap"
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 4 | ||||
-rw-r--r-- | core/jni/android/graphics/BitmapFactory.cpp | 256 | ||||
-rw-r--r-- | graphics/java/android/graphics/BitmapFactory.java | 65 |
3 files changed, 221 insertions, 104 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 04ad649..4eb70ab 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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(); diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index c9eb640..dcd1d28 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -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) { + int sampleSize, bool ditherImage) { + SkImageRef* pr; // only use ashmem for large images, since mmaps come at a price if (bitmap->getSize() >= 32 * 1024) { @@ -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); @@ -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"); } @@ -148,35 +205,35 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); - NinePatchPeeker peeker(decoder); - JavaPixelAllocator javaAllocator(env); - SkBitmap* bitmap; + NinePatchPeeker peeker(decoder); + JavaPixelAllocator javaAllocator(env); + + SkBitmap* bitmap; if (javaBitmap == NULL) { bitmap = new SkBitmap; } else { if (sampleSize != 1) { return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1"); } - bitmap = (SkBitmap *) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID); + bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID); // 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<SkImageDecoder> add(decoder); + SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL); decoder->setPeeker(&peeker); if (!isPurgeable) { decoder->setAllocator(&javaAllocator); } - AutoDecoderCancel adc(options, decoder); + AutoDecoderCancel adc(options, decoder); // 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"); } @@ -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())); + 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); } @@ -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); } @@ -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) { @@ -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); @@ -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); @@ -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 { @@ -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) { + 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); @@ -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; @@ -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;", @@ -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 diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index f0d1643..c5705f6 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -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 @@ -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 @@ -477,11 +479,30 @@ public class BitmapFactory { // value should be. is.mark(1024); - Bitmap bm; + 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 @@ -490,13 +511,32 @@ public class BitmapFactory { byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[16 * 1024]; - bm = nativeDecodeStream(is, tempStorage, outPadding, opts); + + 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) { @@ -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); - bm.setNinePatchChunk(np); + if (isNinePatch) { + np = nativeScaleNinePatch(np, scale, outPadding); + bm.setNinePatchChunk(np); + } } + bm.setDensity(targetDensity); } @@ -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); |