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

Commit 0c01dbf8 authored by Leon Scroggins III's avatar Leon Scroggins III Committed by Leon Scroggins
Browse files

ImageDecoder (BitmapFactory 2.0)

Bug: 63909536
Bug: 63908092

Test: CTS: I0f36ce34c968fd7fae4d8edebabea3a421859615

One-pager:
https://docs.google.com/document/d/1IWSdXb5O9lu-Zbj7SaNWo5pS7-FHlonFnqazjnecozM/
Design doc:
https://docs.google.com/document/d/15S6DSAV4EwOuJLv29UC_9cdSGdPg3KvOJVn2EHoP3fw/

ImageDecoder is designed to streamline certain patterns of BitmapFactory
use:
- choosing sample size based on actual dimensions
- choosing a specific output size
- post-processing (e.g. for rounded corners)
- copying to HARDWARE
- decode directly to ashmem
- creating a Drawable
- use as an alpha mask
- save RAM (e.g. use RGB_565)

In addition, it will include new features:
- animated drawables (TODO)
- report failures *and* optionally create a partial image
- crop

Add PostProcess to handle post-processing. It is separate from
ImageDecoder so that it may be used in the future by other commands that
might want something similar (e.g. capturing a View).

Consolidate NinePatch code for sharing between BitmapFactory and
ImageDecoder.

Some features left out of this CL:
- Create from ContentResolver + URI
- animation
- report more info in ImageInfo
- more overloads (e.g. null OnHeaderDecodedListener)

Change-Id: Icf011dc1b97b492788e47cf51fcf8abe8e9c7b88
parent f7314652
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ cc_library_shared {
        "android_graphics_Picture.cpp",
        "android/graphics/Bitmap.cpp",
        "android/graphics/BitmapFactory.cpp",
        "android/graphics/ByteBufferStreamAdaptor.cpp",
        "android/graphics/Camera.cpp",
        "android/graphics/CanvasProperty.cpp",
        "android/graphics/ColorFilter.cpp",
@@ -134,6 +135,7 @@ cc_library_shared {
        "android/graphics/GraphicBuffer.cpp",
        "android/graphics/Graphics.cpp",
        "android/graphics/HarfBuzzNGFaceSkia.cpp",
        "android/graphics/ImageDecoder.cpp",
        "android/graphics/Interpolator.cpp",
        "android/graphics/MaskFilter.cpp",
        "android/graphics/Matrix.cpp",
+4 −0
Original line number Diff line number Diff line
@@ -57,10 +57,12 @@ extern int register_android_os_Process(JNIEnv* env);
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -1380,6 +1382,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_graphics_Bitmap),
    REG_JNI(register_android_graphics_BitmapFactory),
    REG_JNI(register_android_graphics_BitmapRegionDecoder),
    REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
    REG_JNI(register_android_graphics_Camera),
    REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
    REG_JNI(register_android_graphics_CanvasProperty),
@@ -1387,6 +1390,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_graphics_DrawFilter),
    REG_JNI(register_android_graphics_FontFamily),
    REG_JNI(register_android_graphics_GraphicBuffer),
    REG_JNI(register_android_graphics_ImageDecoder),
    REG_JNI(register_android_graphics_Interpolator),
    REG_JNI(register_android_graphics_MaskFilter),
    REG_JNI(register_android_graphics_Matrix),
+3 −59
Original line number Diff line number Diff line
@@ -47,9 +47,6 @@ jfieldID gOptions_bitmapFieldID;

jfieldID gBitmap_ninePatchInsetsFieldID;

jclass gInsetStruct_class;
jmethodID gInsetStruct_constructorMethodID;

jclass gBitmapConfig_class;
jmethodID gBitmapConfig_nativeToConfigMethodID;

@@ -99,43 +96,6 @@ jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
    return jstr;
}

static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
    for (int i = 0; i < count; i++) {
        divs[i] = int32_t(divs[i] * scale + 0.5f);
        if (i > 0 && divs[i] == divs[i - 1]) {
            divs[i]++; // avoid collisions
        }
    }

    if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
        // if the collision avoidance above put some divs outside the bounds of the bitmap,
        // slide outer stretchable divs inward to stay within bounds
        int highestAvailable = maxValue;
        for (int i = count - 1; i >= 0; i--) {
            divs[i] = highestAvailable;
            if (i > 0 && divs[i] <= divs[i-1]){
                // keep shifting
                highestAvailable = divs[i] - 1;
            } else {
                break;
            }
        }
    }
}

static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
        int scaledWidth, int scaledHeight) {
    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);

    // The max value for the divRange is one pixel less than the actual max to ensure that the size
    // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
    scaleDivRange(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
    scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
}

class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
public:
    ScaleCheckingAllocator(float scale, int size)
@@ -428,7 +388,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
    jbyteArray ninePatchChunk = NULL;
    if (peeker.mPatch != NULL) {
        if (willScale) {
            scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
            peeker.scale(scale, scale, scaledWidth, scaledHeight);
        }

        size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -448,12 +408,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,

    jobject ninePatchInsets = NULL;
    if (peeker.mHasInsets) {
        ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
                peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
                peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
                peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
                peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
                peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
        ninePatchInsets = peeker.createNinePatchInsets(env, scale);
        if (ninePatchInsets == NULL) {
            return nullObjectReturn("nine patch insets == null");
        }
@@ -508,13 +463,7 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
    }

    if (padding) {
        if (peeker.mPatch != NULL) {
            GraphicsJNI::set_jrect(env, padding,
                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
        } else {
            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
        }
        peeker.getPadding(env, padding);
    }

    // If we get here, the outputBitmap should have an installed pixelref.
@@ -705,11 +654,6 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
    gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
            "Landroid/graphics/NinePatch$InsetStruct;");

    gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
        "android/graphics/NinePatch$InsetStruct"));
    gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
                                                        "(IIIIIIIIFIF)V");

    gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
            "android/graphics/Bitmap$Config"));
    gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
+323 −0
Original line number Diff line number Diff line
#include "ByteBufferStreamAdaptor.h"
#include "core_jni_helpers.h"

#include <SkStream.h>

using namespace android;

static jmethodID gByteBuffer_getMethodID;
static jmethodID gByteBuffer_setPositionMethodID;

static JNIEnv* get_env_or_die(JavaVM* jvm) {
    JNIEnv* env;
    if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
    }
    return env;
}

class ByteBufferStream : public SkStreamAsset {
private:
    ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
                     jbyteArray storage)
            : mJvm(jvm)
            , mByteBuffer(jbyteBuffer)
            , mPosition(0)
            , mInitialPosition(initialPosition)
            , mLength(length)
            , mStorage(storage) {}

public:
    static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer,
                                    size_t position, size_t length) {
        // This object outlives its native method call.
        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
        if (!jbyteBuffer) {
            return nullptr;
        }

        jbyteArray storage = env->NewByteArray(kStorageSize);
        if (!storage) {
            env->DeleteGlobalRef(jbyteBuffer);
            return nullptr;
        }

        // This object outlives its native method call.
        storage = static_cast<jbyteArray>(env->NewGlobalRef(storage));
        if (!storage) {
            env->DeleteGlobalRef(jbyteBuffer);
            return nullptr;
        }

        return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage);
    }

    ~ByteBufferStream() override {
        auto* env = get_env_or_die(mJvm);
        env->DeleteGlobalRef(mByteBuffer);
        env->DeleteGlobalRef(mStorage);
    }

    size_t read(void* buffer, size_t size) override {
        if (size > mLength - mPosition) {
            size = mLength - mPosition;
        }
        if (!size) {
            return 0;
        }

        if (!buffer) {
            return this->setPosition(mPosition + size);
        }

        auto* env = get_env_or_die(mJvm);
        size_t bytesRead = 0;
        do {
            const size_t requested = (size > kStorageSize) ? kStorageSize : size;
            const jint jrequested = static_cast<jint>(requested);
            env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested);
            if (env->ExceptionCheck()) {
                ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?");
                env->ExceptionDescribe();
                env->ExceptionClear();
                mPosition = mLength;
                return bytesRead;
            }

            env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer));
            if (env->ExceptionCheck()) {
                ALOGE("Internal error in ByteBufferStream::read");
                env->ExceptionDescribe();
                env->ExceptionClear();
                mPosition = mLength;
                return bytesRead;
            }

            mPosition += requested;
            buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested);
            bytesRead += requested;
            size -= requested;
        } while (size);
        return bytesRead;
    }

    bool isAtEnd() const override { return mLength == mPosition; }

    // SkStreamRewindable overrides
    bool rewind() override { return this->setPosition(0); }

    SkStreamAsset* onDuplicate() const override {
        // SkStreamRewindable requires overriding this, but it is not called by
        // decoders, so does not need a true implementation. A proper
        // implementation would require duplicating the ByteBuffer, which has
        // its own internal position state.
        return nullptr;
    }

    // SkStreamSeekable overrides
    size_t getPosition() const override { return mPosition; }

    bool seek(size_t position) override {
        return this->setPosition(position > mLength ? mLength : position);
    }

    bool move(long offset) override {
        long newPosition = mPosition + offset;
        if (newPosition < 0) {
            return this->setPosition(0);
        }
        return this->seek(static_cast<size_t>(newPosition));
    }

    SkStreamAsset* onFork() const override {
        // SkStreamSeekable requires overriding this, but it is not called by
        // decoders, so does not need a true implementation. A proper
        // implementation would require duplicating the ByteBuffer, which has
        // its own internal position state.
        return nullptr;
    }

    // SkStreamAsset overrides
    size_t getLength() const override { return mLength; }

private:
    JavaVM*          mJvm;
    jobject          mByteBuffer;
    // Logical position of the SkStream, between 0 and mLength.
    size_t           mPosition;
    // Initial position of mByteBuffer, treated as mPosition 0.
    const size_t     mInitialPosition;
    // Logical length of the SkStream, from mInitialPosition to
    // mByteBuffer.limit().
    const size_t     mLength;

    // Range has already been checked by the caller.
    bool setPosition(size_t newPosition) {
        auto* env = get_env_or_die(mJvm);
        env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
                              newPosition + mInitialPosition);
        if (env->ExceptionCheck()) {
            ALOGE("Internal error in ByteBufferStream::setPosition");
            env->ExceptionDescribe();
            env->ExceptionClear();
            mPosition = mLength;
            return false;
        }
        mPosition = newPosition;
        return true;
    }

    // FIXME: This is an arbitrary storage size, which should be plenty for
    // some formats (png, gif, many bmps). But for jpeg, the more we can supply
    // in one call the better, and webp really wants all of the data. How to
    // best choose the amount of storage used?
    static constexpr size_t kStorageSize = 4096;
    jbyteArray mStorage;
};

class ByteArrayStream : public SkStreamAsset {
private:
    ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length)
            : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {}

public:
    static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset,
                                   size_t length) {
        // This object outlives its native method call.
        jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray));
        if (!jarray) {
            return nullptr;
        }
        return new ByteArrayStream(jvm, jarray, offset, length);
    }

    ~ByteArrayStream() override {
        auto* env = get_env_or_die(mJvm);
        env->DeleteGlobalRef(mByteArray);
    }

    size_t read(void* buffer, size_t size) override {
        if (size > mLength - mPosition) {
            size = mLength - mPosition;
        }
        if (!size) {
            return 0;
        }

        auto* env = get_env_or_die(mJvm);
        if (buffer) {
            env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
                                    reinterpret_cast<jbyte*>(buffer));
            if (env->ExceptionCheck()) {
                ALOGE("Internal error in ByteArrayStream::read");
                env->ExceptionDescribe();
                env->ExceptionClear();
                mPosition = mLength;
                return 0;
            }
        }

        mPosition += size;
        return size;
    }

    bool isAtEnd() const override { return mLength == mPosition; }

    // SkStreamRewindable overrides
    bool rewind() override {
        mPosition = 0;
        return true;
    }
    SkStreamAsset* onDuplicate() const override {
        // SkStreamRewindable requires overriding this, but it is not called by
        // decoders, so does not need a true implementation. Note that a proper
        // implementation is fairly straightforward
        return nullptr;
    }

    // SkStreamSeekable overrides
    size_t getPosition() const override { return mPosition; }

    bool seek(size_t position) override {
        mPosition = (position > mLength) ? mLength : position;
        return true;
    }

    bool move(long offset) override {
        long newPosition = mPosition + offset;
        if (newPosition < 0) {
            return this->seek(0);
        }
        return this->seek(static_cast<size_t>(newPosition));
    }

    SkStreamAsset* onFork() const override {
        // SkStreamSeekable requires overriding this, but it is not called by
        // decoders, so does not need a true implementation. Note that a proper
        // implementation is fairly straightforward
        return nullptr;
    }

    // SkStreamAsset overrides
    size_t getLength() const override { return mLength; }

private:
    JavaVM*      mJvm;
    jbyteArray   mByteArray;
    // Offset in mByteArray. Only used when communicating with Java.
    const size_t mOffset;
    // Logical position of the SkStream, between 0 and mLength.
    size_t       mPosition;
    const size_t mLength;
};

struct release_proc_context {
    JavaVM* jvm;
    jobject jbyteBuffer;
};

std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer,
                                                        size_t position, size_t limit) {
    JavaVM* jvm;
    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);

    const size_t length = limit - position;
    void* addr = env->GetDirectBufferAddress(jbyteBuffer);
    if (addr) {
        addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position);
        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
        if (!jbyteBuffer) {
            return nullptr;
        }

        auto* context = new release_proc_context{jvm, jbyteBuffer};
        auto releaseProc = [](const void*, void* context) {
            auto* c = reinterpret_cast<release_proc_context*>(context);
            JNIEnv* env = get_env_or_die(c->jvm);
            env->DeleteGlobalRef(c->jbyteBuffer);
            delete c;
        };
        auto data = SkData::MakeWithProc(addr, length, releaseProc, context);
        // The new SkMemoryStream will read directly from addr.
        return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data)));
    }

    // Non-direct, or direct access is not supported.
    return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position,
                                                              length));
}

std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset,
                                                       size_t length) {
    JavaVM* jvm;
    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);

    return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length));
}

int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) {
    jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer");
    gByteBuffer_getMethodID         = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;");
    gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;");
    return true;
}
+37 −0
Original line number Diff line number Diff line
#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_

#include <jni.h>
#include <memory>

class SkStream;

/**
 * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream.
 *
 * This will special case direct ByteBuffers, but not the case where a byte[]
 * can be used directly. For that, use CreateByteArrayStreamAdaptor.
 *
 * @param jbyteBuffer corresponding to the java ByteBuffer. This method will
 *      add a global ref.
 * @param initialPosition returned by ByteBuffer.position(). Decoding starts
 *      from here.
 * @param limit returned by ByteBuffer.limit().
 *
 * Returns null on failure.
 */
std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer,
                                                        size_t initialPosition, size_t limit);

/**
 * Create an adaptor for treating a Java byte[] as an SkStream.
 *
 * @param offset into the byte[] of the beginning of the data to use.
 * @param length of data to use, starting from offset.
 *
 * Returns null on failure.
 */
std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset,
                                                       size_t length);

#endif  // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
Loading