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

Commit 34bff87b authored by Chris Craik's avatar Chris Craik Committed by Android (Google) Code Review
Browse files

Merge "Add support for post-decode density scaling with reuse"

parents c840a133 905e8246
Loading
Loading
Loading
Loading
+87 −61
Original line number Diff line number Diff line
@@ -29,6 +29,10 @@ jfieldID gOptions_ditherFieldID;
jfieldID gOptions_purgeableFieldID;
jfieldID gOptions_shareableFieldID;
jfieldID gOptions_preferQualityOverSpeedFieldID;
jfieldID gOptions_scaledFieldID;
jfieldID gOptions_densityFieldID;
jfieldID gOptions_screenDensityFieldID;
jfieldID gOptions_targetDensityFieldID;
jfieldID gOptions_widthFieldID;
jfieldID gOptions_heightFieldID;
jfieldID gOptions_mimeFieldID;
@@ -152,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)
@@ -165,8 +206,8 @@ public:

    virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
        if (!bitmap->getSize64().is32() || bitmap->getSize() > mSize) {
            ALOGW("bitmap marked for reuse (%d bytes) too small to contain new bitmap (%d bytes)",
                    bitmap->getSize(), mSize);
            ALOGW("bitmap marked for reuse (%d bytes) can't fit new bitmap (%d bytes)",
                    mSize, bitmap->getSize());
            return false;
        }
        bitmap->setPixelRef(mPixelRef);
@@ -183,8 +224,7 @@ private:
// i.e. dynamically allocated, since its lifetime may exceed the current stack
// frame.
static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        jobject options, bool allowPurgeable, bool forcePurgeable = false,
        bool applyScale = false, float scale = 1.0f) {
        jobject options, bool allowPurgeable, bool forcePurgeable = false) {

    int sampleSize = 1;

@@ -193,9 +233,8 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,

    bool doDither = true;
    bool isMutable = false;
    bool willScale = applyScale && scale != 1.0f;
    bool isPurgeable = !willScale &&
            (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
    float scale = 1.0f;
    bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
    bool preferQualityOverSpeed = false;

    jobject javaBitmap = NULL;
@@ -218,13 +257,20 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        preferQualityOverSpeed = env->GetBooleanField(options,
                gOptions_preferQualityOverSpeedFieldID);
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
    }

    // TODO: allow scaling with reuse, ideally avoiding decode in not-enough-space condition
    if (willScale && javaBitmap != NULL) {
        return nullObjectReturn("Cannot pre-scale a reused bitmap");
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;
            }
        }
    }

    const bool willScale = scale != 1.0f;
    isPurgeable &= !willScale;

    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    if (decoder == NULL) {
        return nullObjectReturn("SkImageDecoder::Factory returned null");
@@ -254,16 +300,6 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,

    NinePatchPeeker peeker(decoder);
    decoder->setPeeker(&peeker);
    JavaPixelAllocator javaAllocator(env);
    RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);

    // allocator to be used for final allocation associated with output
    SkBitmap::Allocator* allocator = (javaBitmap != NULL) ?
            (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;

    if (!isPurgeable && !willScale) {
        decoder->setAllocator(allocator);
    }

    AutoDecoderCancel adc(options, decoder);

@@ -276,6 +312,22 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,

    SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;


    JavaPixelAllocator javaAllocator(env);
    RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
    ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
    SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ?
            (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator;
    if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
        if (!willScale) {
            decoder->setAllocator(outputAllocator);
        } else if (javaBitmap != NULL) {
            // check for eventual scaled bounds at allocation time, so we don't decode the bitmap
            // only to find the scaled result too large to fit in the allocation
            decoder->setAllocator(&scaleCheckingAllocator);
        }
    }

    SkBitmap decodingBitmap;
    if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) {
        return nullObjectReturn("decoder->decode returned false");
@@ -353,20 +405,11 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        const float sx = scaledWidth / float(decodingBitmap.width());
        const float sy = scaledHeight / float(decodingBitmap.height());

        SkBitmap::Config config = decodingBitmap.config();
        switch (config) {
            case SkBitmap::kNo_Config:
            case SkBitmap::kIndex8_Config:
            case SkBitmap::kRLE_Index8_Config:
                config = SkBitmap::kARGB_8888_Config;
                break;
            default:
                break;
        }

        // TODO: avoid copying when scaled size equals decodingBitmap size
        SkBitmap::Config config = configForScaledOutput(decodingBitmap.config());
        outputBitmap->setConfig(config, scaledWidth, scaledHeight);
        outputBitmap->setIsOpaque(decodingBitmap.isOpaque());
        if (!outputBitmap->allocPixels(allocator, NULL)) {
        if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
            return nullObjectReturn("allocation failed for scaled bitmap");
        }
        outputBitmap->eraseColor(0);
@@ -422,26 +465,20 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
            isMutable, ninePatchChunk, layoutBounds, -1);
}

static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);

    if (stream) {
        // for now we don't allow purgeable with java inputstreams
        bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale);
        bitmap = doDecode(env, stream, padding, options, false, false);
        stream->unref();
    }
    return bitmap;
}

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    return nativeDecodeStreamScaled(env, clazz, is, storage, padding, options, false, 1.0f);
}

static ssize_t getFDSize(int fd) {
    off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
    if (curr < 0) {
@@ -516,8 +553,8 @@ static SkStream* copyAssetToStream(Asset* asset) {
    return stream;
}

static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset,
        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
        jobject padding, jobject options) {

    SkStream* stream;
    Asset* asset = reinterpret_cast<Asset*>(native_asset);
@@ -535,13 +572,7 @@ static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_a
        stream = new AssetStreamAdaptor(asset);
    }
    SkAutoUnref aur(stream);
    return doDecode(env, stream, padding, options, true, forcePurgeable, applyScale, scale);
}

static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
        jobject padding, jobject options) {

    return nativeDecodeAssetScaled(env, clazz, native_asset, padding, options, false, 1.0f);
    return doDecode(env, stream, padding, options, true, forcePurgeable);
}

static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -575,10 +606,6 @@ static JNINativeMethod gMethods[] = {
        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeStream
    },
    {   "nativeDecodeStream",
        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeStreamScaled
    },

    {   "nativeDecodeFileDescriptor",
        "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
@@ -590,11 +617,6 @@ static JNINativeMethod gMethods[] = {
        (void*)nativeDecodeAsset
    },

    {   "nativeDecodeAsset",
        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeAssetScaled
    },

    {   "nativeDecodeByteArray",
        "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
        (void*)nativeDecodeByteArray
@@ -637,6 +659,10 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
    gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
    gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
            "inPreferQualityOverSpeed", "Z");
    gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z");
    gOptions_densityFieldID = getFieldIDCheck(env, options_class, "inDensity", "I");
    gOptions_screenDensityFieldID = getFieldIDCheck(env, options_class, "inScreenDensity", "I");
    gOptions_targetDensityFieldID = getFieldIDCheck(env, options_class, "inTargetDensity", "I");
    gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
    gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
    gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
+46 −103
Original line number Diff line number Diff line
@@ -50,29 +50,30 @@ public class BitmapFactory {
         * reuse this bitmap when loading content. If the decode operation cannot
         * use this bitmap, the decode method will return <code>null</code> and
         * will throw an IllegalArgumentException. The current implementation
         * necessitates that the reused bitmap be mutable and of a size that is
         * equal to or larger than the source content. The source content must be
         * in jpeg or png format (whether as a resource or as a stream). The
         * {@link android.graphics.Bitmap.Config configuration} of the reused
         * bitmap will override the setting of {@link #inPreferredConfig}, if set.
         * The reused bitmap will continue to remain mutable even when decoding a
         * necessitates that the reused bitmap be mutable, and the resulting
         * reused bitmap will continue to remain mutable even when decoding a
         * resource which would normally result in an immutable bitmap.
         *
         * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, the reused
         * bitmap can be used to decode any other bitmaps as long as the resulting
         * <p>As of {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, any mutable
         * bitmap can be reused to decode any other bitmaps as long as the resulting
         * {@link Bitmap#getByteCount() byte count} of the decoded bitmap is less
         * than or equal to the {@link Bitmap#getAllocationByteCount() allocated byte count}
         * of the reused bitmap. This can be because the intrinsic size is smaller,
         * or the sampled size is smaller. Prior to
         * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, only equal sized
         * bitmaps are supported, with {@link #inSampleSize} set to 1.
         * or the size after density / sampled size scaling is smaller.
         *
         * <p>Prior to {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE} additional
         * constraints apply: The image being decoded (whether as a resource or
         * as a stream) must be in jpeg or png format. Only equal sized bitmaps
         * are supported, with {@link #inSampleSize} set to 1. Additionally, the
         * {@link android.graphics.Bitmap.Config configuration} of the reused
         * bitmap will override the setting of {@link #inPreferredConfig}, if set.
         *
         * <p>You should still always use the returned Bitmap of the decode
         * method and not assume that reusing the bitmap worked, due to the
         * constraints outlined above and failure situations that can occur.
         * Checking whether the return value matches the value of the inBitmap
         * set in the Options structure is a way to see if the bitmap was reused,
         * but in all cases you should use the returned Bitmap to make sure
         * set in the Options structure will indicate if the bitmap was reused,
         * but in all cases you should use the Bitmap returned by the decoding function to ensure
         * that you are using the bitmap that was used as the decode destination.</p>
         */
        public Bitmap inBitmap;
@@ -440,6 +441,7 @@ public class BitmapFactory {
        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }
        setDensityFromOptions(bm, opts);
        return bm;
    }

@@ -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.
@@ -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
@@ -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;
    }

@@ -633,7 +578,7 @@ public class BitmapFactory {
            if (bm == null && opts != null && opts.inBitmap != null) {
                throw new IllegalArgumentException("Problem decoding into existing bitmap");
            }
            return finishDecode(bm, outPadding, opts);
            return bm;
        } else {
            FileInputStream fis = new FileInputStream(fd);
            try {
@@ -660,8 +605,6 @@ public class BitmapFactory {

    private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
            Rect padding, Options opts, boolean applyScale, float scale);
    private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
            Rect padding, Options opts);
    private static native Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts);