Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fb758e9 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 546 into donut

* changes:
  Add (hidden for now) purgeable bitmaps
parents 01be1fa7 c70e06bb
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ LOCAL_C_INCLUDES += \
	external/skia/include/core \
	external/skia/include/effects \
	external/skia/include/images \
	external/skia/src/ports \
	external/skia/include/utils \
	external/sqlite/dist \
	external/sqlite/android \
+154 −53
Original line number Diff line number Diff line
#define LOG_TAG "BitmapFactory"

#include "SkImageDecoder.h"
#include "SkImageRef_ashmem.h"
#include "SkImageRef_GlobalPool.h"
#include "SkPixelRef.h"
#include "SkStream.h"
#include "GraphicsJNI.h"
@@ -19,6 +21,8 @@ static jfieldID gOptions_justBoundsFieldID;
static jfieldID gOptions_sampleSizeFieldID;
static jfieldID gOptions_configFieldID;
static jfieldID gOptions_ditherFieldID;
static jfieldID gOptions_purgeableFieldID;
static jfieldID gOptions_shareableFieldID;
static jfieldID gOptions_widthFieldID;
static jfieldID gOptions_heightFieldID;
static jfieldID gOptions_mimeFieldID;
@@ -33,8 +37,6 @@ static jfieldID gFileDescriptor_descriptor;
    #define TRACE_BITMAP(code)
#endif

//#define MIN_SIZE_TO_USE_MMAP    (4*1024)

///////////////////////////////////////////////////////////////////////////////

class AutoDecoderCancel {
@@ -207,7 +209,11 @@ public:
    
    virtual bool rewind() {
        off_t pos = fAsset->seek(0, SEEK_SET);
        return pos != (off_t)-1;
        if (pos == (off_t)-1) {
            SkDebugf("----- fAsset->seek(rewind) failed\n");
            return false;
        }
        return true;
    }
    
    virtual size_t read(void* buffer, size_t size) {
@@ -222,15 +228,20 @@ public:

            off_t oldOffset = fAsset->seek(0, SEEK_CUR);
            if (-1 == oldOffset) {
                SkDebugf("---- fAsset->seek(oldOffset) failed\n");
                return 0;
            }
            off_t newOffset = fAsset->seek(size, SEEK_CUR);
            if (-1 == newOffset) {
                SkDebugf("---- fAsset->seek(%d) failed\n", size);
                return 0;
            }
            amount = newOffset - oldOffset;
        } else {
            amount = fAsset->read(buffer, size);
            if (amount <= 0) {
                SkDebugf("---- fAsset->read(%d) returned %d\n", size, amount);
            }
        }
        
        if (amount < 0) {
@@ -279,13 +290,46 @@ static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
    return jstr;
}

static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
                        jobject options) {
static bool optionsPurgeable(JNIEnv* env, jobject options) {
    return options != NULL &&
            env->GetBooleanField(options, gOptions_purgeableFieldID);
}

static bool optionsShareable(JNIEnv* env, jobject options) {
    return options != NULL &&
            env->GetBooleanField(options, gOptions_shareableFieldID);
}

static jobject nullObjectReturn(const char msg[]) {
    if (msg) {
        SkDebugf("--- %s\n", msg);
    }
    return NULL;
}

static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
                                   int sampleSize) {
    SkPixelRef* pr;
    // only use ashmem for large images, since mmaps come at a price
    if (bitmap->getSize() >= 32 * 65536) {
        pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
    } else {
        pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
    }
    bitmap->setPixelRef(pr)->unref();
    return pr;
}

// since we "may" create a purgeable imageref, we require the stream be ref'able
// 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) {
    int sampleSize = 1;
    SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
    SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
    bool doDither = true;
    bool isPurgeable = allowPurgeable && optionsPurgeable(env, options);
    
    if (NULL != options) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
@@ -304,14 +348,14 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,

    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    if (NULL == decoder) {
        return NULL;
        return nullObjectReturn("SkImageDecoder::Factory returned null");
    }
    
    decoder->setSampleSize(sampleSize);
    decoder->setDitherImage(doDither);

    NinePatchPeeker     peeker;
    JavaPixelAllocator  allocator(env);
    JavaPixelAllocator  javaAllocator(env);
    SkBitmap*           bitmap = new SkBitmap;
    Res_png_9patch      dummy9Patch;

@@ -319,21 +363,25 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
    SkAutoTDelete<SkBitmap>         adb(bitmap);

    decoder->setPeeker(&peeker);
    decoder->setAllocator(&allocator);
    if (!isPurgeable) {
        decoder->setAllocator(&javaAllocator);
    }

    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) {
        if (env->GetBooleanField(options, gOptions_mCancelID)) {
            return NULL;
        }
    if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
        return nullObjectReturn("gOptions_mCancelID");;
    }

    if (!decoder->decode(stream, bitmap, prefConfig, mode)) {
        return NULL;
    SkImageDecoder::Mode decodeMode = mode;
    if (isPurgeable) {
        decodeMode = SkImageDecoder::kDecodeBounds_Mode;
    }
    if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) {
        return nullObjectReturn("decoder->decode returned false");
    }

    // update options (if any)
@@ -357,12 +405,12 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
        if (NULL == ninePatchChunk) {
            return NULL;
            return nullObjectReturn("ninePatchChunk == null");
        }
        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
                                                              NULL);
        if (NULL == array) {
            return NULL;
            return nullObjectReturn("primitive array == null");
        }
        peeker.fPatch->serialize(array);
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
@@ -383,11 +431,17 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        }
    }

    SkPixelRef* pr;
    if (isPurgeable) {
        pr = installPixelRef(bitmap, stream, sampleSize);
    } else {
        // if we get here, we're in kDecodePixels_Mode and will therefore
        // already have a pixelref installed.
        pr = bitmap->pixelRef();
    }
    // promise we will never change our pixels (great for sharing and pictures)
    SkPixelRef* ref = bitmap->pixelRef();
    SkASSERT(ref);
    ref->setImmutable();

    pr->setImmutable();
    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk);
}

@@ -400,7 +454,8 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage);

    if (stream) {
        bitmap = doDecode(env, stream, padding, options);
        // for now we don't allow purgeable with java inputstreams
        bitmap = doDecode(env, stream, padding, options, false);
        stream->unref();
    }
    return bitmap;
@@ -442,51 +497,95 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
    jint descriptor = env->GetIntField(fileDescriptor,
                                       gFileDescriptor_descriptor);

#ifdef MIN_SIZE_TO_USE_MMAP
    // First try to use mmap
    size_t size = getFDSize(descriptor);
    if (size >= MIN_SIZE_TO_USE_MMAP) {
        void* addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, descriptor, 0);
//        SkDebugf("-------- mmap returned %p %d\n", addr, size);
        if (MAP_FAILED != addr) {
            SkMemoryStream strm(addr, size);
            jobject obj = doDecode(env, &strm, padding, bitmapFactoryOptions);
            munmap(addr, size);
            return obj;
    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
    bool isShareable = optionsShareable(env, bitmapFactoryOptions);
    bool weOwnTheFD = false;
    if (isPurgeable && isShareable) {
        int newFD = ::dup(descriptor);
        if (-1 != newFD) {
            weOwnTheFD = true;
            descriptor = newFD;
        }
    }
#endif

    // we pass false for closeWhenDone, since the caller owns the descriptor    
    SkFDStream file(descriptor, false);
    if (!file.isValid()) {
    SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
    SkAutoUnref aur(stream);
    if (!stream->isValid()) {
        return NULL;
    }

    /* Restore our offset when we leave, so the caller doesn't have to.
       This is a real feature, so we can be called more than once with the
       same descriptor.
    /* Restore our offset when we leave, so we can be called more than once
       with the same descriptor. This is only required if we didn't dup the
       file descriptor, but it is OK to do it all the time.
    */
    AutoFDSeek as(descriptor);

    return doDecode(env, &file, padding, bitmapFactoryOptions);
    return doDecode(env, stream, padding, bitmapFactoryOptions, true);
}

/*  make a deep copy of the asset, and return it as a stream, or NULL if there
    was an error.
 */
static SkStream* copyAssetToStream(Asset* asset) {
    // if we could "ref/reopen" the asset, we may not need to copy it here
    off_t size = asset->seek(0, SEEK_SET);
    if ((off_t)-1 == size) {
        SkDebugf("---- copyAsset: asset rewind failed\n");
        return NULL;
    }

    size = asset->getLength();
    if (size <= 0) {
        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
        return NULL;
    }

    SkStream* stream = new SkMemoryStream(size);
    void* data = const_cast<void*>(stream->getMemoryBase());
    off_t len = asset->read(data, size);
    if (len != size) {
        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
        delete stream;
        stream = NULL;
    }
    return stream;
}

static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
                                 jint native_asset,    // Asset
                                 jobject padding,       // Rect
                                 jobject options) { // BitmapFactory$Options
    AssetStreamAdaptor  mystream((Asset*)native_asset);

    return doDecode(env, &mystream, padding, options);
    SkStream* stream;
    Asset* asset = reinterpret_cast<Asset*>(native_asset);

    if (optionsPurgeable(env, options)) {
        // 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) {
            return NULL;
        }
    } else {
        // since we know we'll be done with the asset when we return, we can
        // just use a simple wrapper
        stream = new AssetStreamAdaptor(asset);
    }
    SkAutoUnref aur(stream);
    return doDecode(env, stream, padding, options, true);
}

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().
     */
    AutoJavaByteArray ar(env, byteArray);
    SkMemoryStream  stream(ar.ptr() + offset, length);

    return doDecode(env, &stream, NULL, options);
    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length,
                                          optionsPurgeable(env, options));
    SkAutoUnref aur(stream);
    return doDecode(env, stream, NULL, options, true);
}

static void nativeRequestCancel(JNIEnv*, jobject joptions) {
@@ -595,6 +694,8 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
    gOptions_configFieldID = getFieldIDCheck(env, gOptions_class, "inPreferredConfig",
            "Landroid/graphics/Bitmap$Config;");
    gOptions_ditherFieldID = getFieldIDCheck(env, gOptions_class, "inDither", "Z");
    gOptions_purgeableFieldID = getFieldIDCheck(env, gOptions_class, "inPurgeable", "Z");
    gOptions_shareableFieldID = getFieldIDCheck(env, gOptions_class, "inInputShareable", "Z");
    gOptions_widthFieldID = getFieldIDCheck(env, gOptions_class, "outWidth", "I");
    gOptions_heightFieldID = getFieldIDCheck(env, gOptions_class, "outHeight", "I");
    gOptions_mimeFieldID = getFieldIDCheck(env, gOptions_class, "outMimeType", "Ljava/lang/String;");
+30 −0
Original line number Diff line number Diff line
@@ -102,6 +102,36 @@ public class BitmapFactory {
         */
        public boolean inScaled;

        /**
         * If this is set to true, then the resulting bitmap will allocate its
         * pixels such that they can be purged if the system needs to reclaim
         * memory. In that instance, when the pixels need to be accessed again
         * (e.g. the bitmap is drawn, getPixels() is called), they will be
         * automatically re-decoded.
         *
         * For the re-decode to happen, the bitmap must have access to the
         * encoded data, either by sharing a reference to the input
         * or by making a copy of it. This distinction is controlled by
         * inInputShareable. If this is true, then the bitmap may keep a shallow
         * reference to the input. If this is false, then the bitmap will
         * explicitly make a copy of the input data, and keep that. Even if
         * sharing is allowed, the implementation may still decide to make a
         * deep copy of the input data.
         *
         * @hide pending API council approval
         */
        public boolean inPurgeable;

        /**
         * This field works in conjuction with inPurgeable. If inPurgeable is
         * false, then this field is ignored. If inPurgeable is true, then this
         * field determines whether the bitmap can share a reference to the
         * input data (inputstream, array, etc.) or if it must make a deep copy.
         *
         * @hide pending API council approval
         */
        public boolean inInputShareable;

        /**
         * The resulting width of the bitmap, set independent of the state of
         * inJustDecodeBounds. However, if there is an error trying to decode,