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

Commit 7e8c03c0 authored by Chris Craik's avatar Chris Craik
Browse files

Support all formats for BitmapFactory.Options.inBitmap

bug:9194265

Instead of using custom code in skia to avoid allocations, use a
custom allocator that reuses the allocations from the inBitmap.

In order to avoid inconsistent state, the decode is done in a
separate bitmap and swapped into the existing native bitmap.

Eventually, we'd like to support inScaled=true completely avoiding
java allocations.

Change-Id: Ic4a2f2373b100a80a32c1cdebb7bcb726711c8a7
parent 3bf4fa4b
Loading
Loading
Loading
Loading
+72 −69
Original line number Diff line number Diff line
@@ -152,6 +152,33 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
    return pr;
}

class RecyclingPixelAllocator : public SkBitmap::Allocator {
public:
    RecyclingPixelAllocator(SkPixelRef* pixelRef, unsigned int size)
        : mPixelRef(pixelRef), mSize(size) {
        SkSafeRef(mPixelRef);
    }

    ~RecyclingPixelAllocator() {
        SkSafeUnref(mPixelRef);
    }

    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);
            return false;
        }
        bitmap->setPixelRef(mPixelRef);
        bitmap->lockPixels();
        return true;
    }

private:
    SkPixelRef* const mPixelRef;
    const unsigned int mSize;
};

// 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.
@@ -193,6 +220,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        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");
    }
@@ -206,34 +234,35 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
    decoder->setDitherImage(doDither);
    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);

    NinePatchPeeker peeker(decoder);
    JavaPixelAllocator javaAllocator(env);

    SkBitmap* bitmap;
    bool useExistingBitmap = false;
    SkBitmap* outputBitmap = NULL;
    unsigned int existingBufferSize = 0;
    if (javaBitmap == NULL) {
        bitmap = new SkBitmap;
    if (javaBitmap != NULL) {
        outputBitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
        if (outputBitmap->isImmutable()) {
            ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
            javaBitmap = NULL;
            outputBitmap = NULL;
        } else {
        bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
        // only reuse the provided bitmap if it is mutable
        if (!bitmap->isImmutable()) {
            useExistingBitmap = true;
            // config of supplied bitmap overrules config set in options
            prefConfig = bitmap->getConfig();
            existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
        } else {
            ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
            bitmap = new SkBitmap;
        }
    }

    SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL);
    if (outputBitmap == NULL) outputBitmap = adb.get();

    SkAutoTDelete<SkImageDecoder> add(decoder);
    SkAutoTDelete<SkBitmap> adb(!useExistingBitmap ? bitmap : NULL);

    NinePatchPeeker peeker(decoder);
    decoder->setPeeker(&peeker);
    if (!isPurgeable) {
        decoder->setAllocator(&javaAllocator);
    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);
@@ -245,45 +274,15 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        return nullObjectReturn("gOptions_mCancelID");
    }

    SkImageDecoder::Mode decodeMode = mode;
    if (isPurgeable) {
        decodeMode = SkImageDecoder::kDecodeBounds_Mode;
    }

    if (javaBitmap != NULL) {
        // If we're reusing the pixelref from an existing bitmap, decode the bounds and
        // reinitialize the native object for the new content, keeping the pixelRef
        SkPixelRef* pixelRef = bitmap->pixelRef();
        SkSafeRef(pixelRef);

        SkBitmap boundsBitmap;
        decoder->decode(stream, &boundsBitmap, prefConfig, SkImageDecoder::kDecodeBounds_Mode);
        stream->rewind();
    SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;

        if (boundsBitmap.getSize() > existingBufferSize) {
            return nullObjectReturn("bitmap marked for reuse too small to contain decoded data");
        }

        bitmap->setConfig(boundsBitmap.config(), boundsBitmap.width(), boundsBitmap.height(), 0);
        bitmap->setPixelRef(pixelRef);
        SkSafeUnref(pixelRef);
        GraphicsJNI::reinitBitmap(env, javaBitmap);
    }

    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)) {
    SkBitmap decodingBitmap;
    if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) {
        return nullObjectReturn("decoder->decode returned false");
    }

    int scaledWidth = decoded->width();
    int scaledHeight = decoded->height();
    int scaledWidth = decodingBitmap.width();
    int scaledHeight = decodingBitmap.height();

    if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
        scaledWidth = int(scaledWidth * scale + 0.5f);
@@ -351,10 +350,10 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
        // 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());
        const float sx = scaledWidth / float(decodingBitmap.width());
        const float sy = scaledHeight / float(decodingBitmap.height());

        SkBitmap::Config config = decoded->config();
        SkBitmap::Config config = decodingBitmap.config();
        switch (config) {
            case SkBitmap::kNo_Config:
            case SkBitmap::kIndex8_Config:
@@ -365,19 +364,21 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
                break;
        }

        bitmap->setConfig(config, scaledWidth, scaledHeight);
        bitmap->setIsOpaque(decoded->isOpaque());
        if (!bitmap->allocPixels(&javaAllocator, NULL)) {
        outputBitmap->setConfig(config, scaledWidth, scaledHeight);
        outputBitmap->setIsOpaque(decodingBitmap.isOpaque());
        if (!outputBitmap->allocPixels(allocator, NULL)) {
            return nullObjectReturn("allocation failed for scaled bitmap");
        }
        bitmap->eraseColor(0);
        outputBitmap->eraseColor(0);

        SkPaint paint;
        paint.setFilterBitmap(true);

        SkCanvas canvas(*bitmap);
        SkCanvas canvas(*outputBitmap);
        canvas.scale(sx, sy);
        canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
    } else {
        outputBitmap->swap(decodingBitmap);
    }

    if (padding) {
@@ -392,17 +393,17 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,

    SkPixelRef* pr;
    if (isPurgeable) {
        pr = installPixelRef(bitmap, stream, sampleSize, doDither);
        pr = installPixelRef(outputBitmap, stream, sampleSize, doDither);
    } else {
        // if we get here, we're in kDecodePixels_Mode and will therefore
        // already have a pixelref installed.
        pr = bitmap->pixelRef();
        pr = outputBitmap->pixelRef();
    }
    if (pr == NULL) {
        return nullObjectReturn("Got null SkPixelRef");
    }

    if (!isMutable && !useExistingBitmap) {
    if (!isMutable && javaBitmap == NULL) {
        // promise we will never change our pixels (great for sharing and pictures)
        pr->setImmutable();
    }
@@ -410,12 +411,14 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
    // detach bitmap from its autodeleter, since we want to own it now
    adb.detach();

    if (useExistingBitmap) {
    if (javaBitmap != NULL) {
        GraphicsJNI::reinitBitmap(env, javaBitmap);
        outputBitmap->notifyPixelsChanged();
        // If a java bitmap was passed in for reuse, pass it back
        return javaBitmap;
    }
    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
    return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
            isMutable, ninePatchChunk, layoutBounds, -1);
}

+1 −0
Original line number Diff line number Diff line
@@ -552,6 +552,7 @@ public class BitmapFactory {
        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;