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

Commit 00d70d36 authored by Matt Sarett's avatar Matt Sarett Committed by Android (Google) Code Review
Browse files

Merge "Merge new implementation of BitmapRegionDecoder."

parents 8469b86e 1f979639
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -199,7 +199,6 @@ LOCAL_C_INCLUDES += \
    external/sqlite/android \
    external/expat/lib \
    external/tremor/Tremor \
    external/jpeg \
    external/harfbuzz_ng/src \
    libcore/include \
    $(call include-path-for, audio-utils) \
@@ -238,7 +237,7 @@ LOCAL_SHARED_LIBRARIES := \
    libicuuc \
    libicui18n \
    libmedia \
    libjpeg \
    libjpeg-turbo \
    libusbhost \
    libharfbuzz_ng \
    libz \
+109 −108
Original line number Diff line number Diff line
@@ -16,17 +16,20 @@

#define LOG_TAG "BitmapRegionDecoder"

#include "AutoDecodeCancel.h"
#include "BitmapFactory.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "Utils.h"

#include "SkBitmap.h"
#include "SkBitmapRegionDecoder.h"
#include "SkCodec.h"
#include "SkData.h"
#include "GraphicsJNI.h"
#include "SkImageEncoder.h"
#include "SkEncodedFormat.h"
#include "SkUtils.h"
#include "SkPixelRef.h"
#include "SkStream.h"
#include "Utils.h"

#include "android_nio_utils.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -39,60 +42,54 @@

using namespace android;

class BitmapRegionDecoder {
public:
    BitmapRegionDecoder(SkImageDecoder* decoder, int width, int height) {
        fDecoder = decoder;
        fWidth = width;
        fHeight = height;
    }
    ~BitmapRegionDecoder() {
        delete fDecoder;
// This is very similar to, and based on, getMimeTypeString() in BitmapFactory.
jstring encodedFormatToString(JNIEnv* env, SkEncodedFormat format) {
    const char* mimeType;
    switch (format) {
        case SkEncodedFormat::kBMP_SkEncodedFormat:
            mimeType = "image/bmp";
            break;
        case SkEncodedFormat::kGIF_SkEncodedFormat:
            mimeType = "image/gif";
            break;
        case SkEncodedFormat::kICO_SkEncodedFormat:
            mimeType = "image/x-ico";
            break;
        case SkEncodedFormat::kJPEG_SkEncodedFormat:
            mimeType = "image/jpeg";
            break;
        case SkEncodedFormat::kPNG_SkEncodedFormat:
            mimeType = "image/png";
            break;
        case SkEncodedFormat::kWEBP_SkEncodedFormat:
            mimeType = "image/webp";
            break;
        case SkEncodedFormat::kWBMP_SkEncodedFormat:
            mimeType = "image/vnd.wap.wbmp";
            break;
        default:
            mimeType = nullptr;
            break;
    }

    bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
                      SkColorType pref, int sampleSize) {
        fDecoder->setSampleSize(sampleSize);
        return fDecoder->decodeSubset(bitmap, rect, pref);
    jstring jstr = nullptr;
    if (mimeType != nullptr) {
        jstr = env->NewStringUTF(mimeType);
    }
    return jstr;
}

    SkImageDecoder* getDecoder() const { return fDecoder; }
    int getWidth() const { return fWidth; }
    int getHeight() const { return fHeight; }

private:
    SkImageDecoder* fDecoder;
    int fWidth;
    int fHeight;
};

// Takes ownership of the SkStreamRewindable. For consistency, deletes stream even
// when returning null.
static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) {
    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    int width, height;
    if (NULL == decoder) {
        delete stream;
    SkAutoTDelete<SkBitmapRegionDecoder> brd(
            SkBitmapRegionDecoder::Create(stream, SkBitmapRegionDecoder::kAndroidCodec_Strategy));
    if (NULL == brd) {
        doThrowIOE(env, "Image format not supported");
        return nullObjectReturn("SkImageDecoder::Factory returned null");
        return nullObjectReturn("CreateBitmapRegionDecoder returned null");
    }

    JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env);
    decoder->setAllocator(javaAllocator);
    javaAllocator->unref();

    // This call passes ownership of stream to the decoder, or deletes on failure.
    if (!decoder->buildTileIndex(stream, &width, &height)) {
        char msg[100];
        snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder",
                decoder->getFormatName());
        doThrowIOE(env, msg);
        delete decoder;
        return nullObjectReturn("decoder->buildTileIndex returned false");
    }

    BitmapRegionDecoder *bm = new BitmapRegionDecoder(decoder, width, height);
    return GraphicsJNI::createBitmapRegionDecoder(env, bm);
    return GraphicsJNI::createBitmapRegionDecoder(env, brd.detach());
}

static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -160,102 +157,106 @@ static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,

/*
 * nine patch not supported
 *
 * purgeable not supported
 * reportSizeToVM not supported
 */
static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle,
                                jint start_x, jint start_y, jint width, jint height, jobject options) {
    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
    jobject tileBitmap = NULL;
    SkImageDecoder *decoder = brd->getDecoder();
static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX,
        jint inputY, jint inputWidth, jint inputHeight, jobject options) {

    // Set default options.
    int sampleSize = 1;
    SkColorType prefColorType = kUnknown_SkColorType;
    bool doDither = true;
    bool preferQualityOverSpeed = false;
    bool requireUnpremultiplied = false;
    SkColorType colorType = kN32_SkColorType;
    bool requireUnpremul = false;
    jobject javaBitmap = NULL;

    // Update the default options with any options supplied by the client.
    if (NULL != options) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        // initialize these, in case we fail later on
        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
        if (kAlpha_8_SkColorType == colorType) {
            colorType = kGray_8_SkColorType;
        }
        requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
        // The Java options of ditherMode and preferQualityOverSpeed are deprecated.  We will
        // ignore the values of these fields.

        // Initialize these fields to indicate a failure.  If the decode succeeds, we
        // will update them later on.
        env->SetIntField(options, gOptions_widthFieldID, -1);
        env->SetIntField(options, gOptions_heightFieldID, -1);
        env->SetObjectField(options, gOptions_mimeFieldID, 0);

        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
        prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
        preferQualityOverSpeed = env->GetBooleanField(options,
                gOptions_preferQualityOverSpeedFieldID);
        // Get the bitmap for re-use if it exists.
        tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
        requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
    }

    decoder->setDitherImage(doDither);
    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
    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)) {
        return nullObjectReturn("gOptions_mCancelID");;
    // Recycle a bitmap if possible.
    android::Bitmap* recycledBitmap = nullptr;
    size_t recycledBytes = 0;
    if (javaBitmap) {
        recycledBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
        if (recycledBitmap->peekAtPixelRef()->isImmutable()) {
            ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
        }
        recycledBytes = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
    }

    SkIRect region;
    region.fLeft = start_x;
    region.fTop = start_y;
    region.fRight = start_x + width;
    region.fBottom = start_y + height;
    SkBitmap bitmap;

    if (tileBitmap != NULL) {
        // Re-use bitmap.
        GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap);
    // Set up the pixel allocator
    SkBRDAllocator* allocator = nullptr;
    RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes);
    JavaPixelAllocator javaAlloc(env);
    if (javaBitmap) {
        allocator = &recycleAlloc;
        // We are required to match the color type of the recycled bitmap.
        colorType = recycledBitmap->info().colorType();
    } else {
        allocator = &javaAlloc;
    }

    if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) {
        return nullObjectReturn("decoder->decodeRegion returned false");
    // Decode the region.
    SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
    SkBitmapRegionDecoder* brd =
            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
    SkBitmap bitmap;
    if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize, colorType, requireUnpremul)) {
        return nullObjectReturn("Failed to decode region.");
    }

    // update options (if any)
    // If the client provided options, indicate that the decode was successful.
    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?
        env->SetObjectField(options, gOptions_mimeFieldID,
                            getMimeTypeString(env, decoder->getFormat()));
                encodedFormatToString(env, brd->getEncodedFormat()));
    }

    if (tileBitmap != NULL) {
        bitmap.notifyPixelsChanged();
        return tileBitmap;
    // If we may have reused a bitmap, we need to indicate that the pixels have changed.
    if (javaBitmap) {
        recycleAlloc.copyIfNecessary();
        return javaBitmap;
    }

    JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();

    int bitmapCreateFlags = 0;
    if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
    return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(),
            bitmapCreateFlags);
    if (!requireUnpremul) {
        bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
    }
    return GraphicsJNI::createBitmap(env, javaAlloc.getStorageObjAndReset(), bitmapCreateFlags);
}

static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
    return static_cast<jint>(brd->getHeight());
    SkBitmapRegionDecoder* brd =
            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
    return static_cast<jint>(brd->height());
}

static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) {
    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
    return static_cast<jint>(brd->getWidth());
    SkBitmapRegionDecoder* brd =
            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
    return static_cast<jint>(brd->width());
}

static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) {
    BitmapRegionDecoder *brd = reinterpret_cast<BitmapRegionDecoder*>(brdHandle);
    SkBitmapRegionDecoder* brd =
            reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle);
    delete brd;
}

+86 −1
Original line number Diff line number Diff line
@@ -439,7 +439,7 @@ int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap)
    return env->CallIntMethod(javaBitmap, gBitmap_getAllocationByteCountMethodID);
}

jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap)
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap)
{
    SkASSERT(bitmap != NULL);

@@ -677,6 +677,91 @@ bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {

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

RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
        android::Bitmap* recycledBitmap, size_t recycledBytes)
    : mRecycledBitmap(recycledBitmap)
    , mRecycledBytes(recycledBytes)
    , mSkiaBitmap(nullptr)
    , mNeedsCopy(false)
{}

RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {}

bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
    // Ensure that the caller did not pass in a NULL bitmap to the constructor or this
    // function.
    LOG_ALWAYS_FATAL_IF(!mRecycledBitmap);
    LOG_ALWAYS_FATAL_IF(!bitmap);
    mSkiaBitmap = bitmap;

    // This behaves differently than the RecyclingPixelAllocator.  For backwards
    // compatibility, the original color type of the recycled bitmap must be maintained.
    if (mRecycledBitmap->info().colorType() != bitmap->colorType()) {
        return false;
    }

    // The Skia bitmap specifies the width and height needed by the decoder.
    // mRecycledBitmap specifies the width and height of the bitmap that we
    // want to reuse.  Neither can be changed.  We will try to find a way
    // to reuse the memory.
    const int maxWidth = SkTMax(bitmap->width(), mRecycledBitmap->info().width());
    const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height());
    const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
    const size_t rowBytes = maxInfo.minRowBytes();
    const size_t bytesNeeded = maxInfo.getSafeSize(rowBytes);
    if (bytesNeeded <= mRecycledBytes) {
        // Here we take advantage of reconfigure() to reset the rowBytes and ctable
        // of mRecycledBitmap.  It is very important that we pass in
        // mRecycledBitmap->info() for the SkImageInfo.  According to the
        // specification for BitmapRegionDecoder, we are not allowed to change
        // the SkImageInfo.
        mRecycledBitmap->reconfigure(mRecycledBitmap->info(), rowBytes, ctable);

        // This call will give the bitmap the same pixelRef as mRecycledBitmap.
        bitmap->setPixelRef(mRecycledBitmap->refPixelRef())->unref();

        // Make sure that the recycled bitmap has the correct alpha type.
        mRecycledBitmap->setAlphaType(bitmap->alphaType());

        bitmap->lockPixels();
        mNeedsCopy = false;

        // TODO: If the dimensions of the SkBitmap are smaller than those of
        // mRecycledBitmap, should we zero the memory in mRecycledBitmap?
        return true;
    }

    // In the event that mRecycledBitmap is not large enough, allocate new memory
    // on the heap.
    SkBitmap::HeapAllocator heapAllocator;

    // We will need to copy from heap memory to mRecycledBitmap's memory after the
    // decode is complete.
    mNeedsCopy = true;

    return heapAllocator.allocPixelRef(bitmap, ctable);
}

void RecyclingClippingPixelAllocator::copyIfNecessary() {
    if (mNeedsCopy) {
        SkPixelRef* recycledPixels = mRecycledBitmap->refPixelRef();
        void* dst = recycledPixels->pixels();
        size_t dstRowBytes = mRecycledBitmap->rowBytes();
        size_t bytesToCopy = SkTMin(mRecycledBitmap->info().minRowBytes(),
                mSkiaBitmap->info().minRowBytes());
        for (int y = 0; y < mRecycledBitmap->info().height(); y++) {
            memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
            dst = SkTAddOffset<void>(dst, dstRowBytes);
        }
        recycledPixels->notifyPixelsChanged();
        recycledPixels->unref();
    }
    mRecycledBitmap = nullptr;
    mSkiaBitmap = nullptr;
}

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

AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) {
    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK,
            "env->GetJavaVM failed");
+72 −3
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@

#include "Bitmap.h"
#include "SkBitmap.h"
#include "SkBRDAllocator.h"
#include "SkCodec.h"
#include "SkDevice.h"
#include "SkPixelRef.h"
#include "SkMallocPixelRef.h"
@@ -12,7 +14,7 @@
#include <Canvas.h>
#include <jni.h>

class BitmapRegionDecoder;
class SkBitmapRegionDecoder;
class SkCanvas;

namespace android {
@@ -90,7 +92,7 @@ public:

    static jobject createRegion(JNIEnv* env, SkRegion* region);

    static jobject createBitmapRegionDecoder(JNIEnv* env, BitmapRegionDecoder* bitmap);
    static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);

    static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
            SkColorTable* ctable);
@@ -123,7 +125,7 @@ public:
 *  ensure that the allocated buffer is properly accounted for with a
 *  reference in the heap (or a JNI global reference).
 */
class JavaPixelAllocator : public SkBitmap::Allocator {
class JavaPixelAllocator : public SkBRDAllocator {
public:
    JavaPixelAllocator(JNIEnv* env);
    ~JavaPixelAllocator();
@@ -139,11 +141,78 @@ public:
        return result;
    };

    /**
     *  Indicates that this allocator allocates zero initialized
     *  memory.
     */
    SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kYes_ZeroInitialized; }

private:
    JavaVM* mJavaVM;
    android::Bitmap* mStorage = nullptr;
};

/**
 *  Allocator to handle reusing bitmaps for BitmapRegionDecoder.
 *
 *  The BitmapRegionDecoder documentation states that, if it is
 *  provided, the recycled bitmap will always be reused, clipping
 *  the decoded output to fit in the recycled bitmap if necessary.
 *  This allocator implements that behavior.
 *
 *  Skia's SkBitmapRegionDecoder expects the memory that
 *  is allocated to be large enough to decode the entire region
 *  that is requested.  It will decode directly into the memory
 *  that is provided.
 *
 *  FIXME: BUG:25465958
 *  If the recycled bitmap is not large enough for the decode
 *  requested, meaning that a clip is required, we will allocate
 *  enough memory for Skia to perform the decode, and then copy
 *  from the decoded output into the recycled bitmap.
 *
 *  If the recycled bitmap is large enough for the decode requested,
 *  we will provide that memory for Skia to decode directly into.
 *
 *  This allocator should only be used for a single allocation.
 *  After we reuse the recycledBitmap once, it is dangerous to
 *  reuse it again, given that it still may be in use from our
 *  first allocation.
 */
class RecyclingClippingPixelAllocator : public SkBRDAllocator {
public:

    RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
            size_t recycledBytes);

    ~RecyclingClippingPixelAllocator();

    virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override;

    /**
     *  Must be called!
     *
     *  In the event that the recycled bitmap is not large enough for
     *  the allocation requested, we will allocate memory on the heap
     *  instead.  As a final step, once we are done using this memory,
     *  we will copy the contents of the heap memory into the recycled
     *  bitmap's memory, clipping as necessary.
     */
    void copyIfNecessary();

    /**
     *  Indicates that this allocator does not allocate zero initialized
     *  memory.
     */
    SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; }

private:
    android::Bitmap* mRecycledBitmap;
    const size_t     mRecycledBytes;
    SkBitmap*        mSkiaBitmap;
    bool             mNeedsCopy;
};

class AshmemPixelAllocator : public SkBitmap::Allocator {
public:
    AshmemPixelAllocator(JNIEnv* env);