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

Commit 47cd8e92 authored by Chris Craik's avatar Chris Craik
Browse files

Implement outline support for nine patches

b/15856895

Nine patches now have outline round rect metadata stored as optional
png tags. aapt generates these automatically by inspecting the bitmap
pixels to estimate outline bounds and round rect radius, based on
opacity.

Change-Id: I226e328a97873010d9e1adb797ac48f93a31183c
parent 5028fb03
Loading
Loading
Loading
Loading
+27 −35
Original line number Diff line number Diff line
@@ -39,14 +39,12 @@ jfieldID gOptions_heightFieldID;
jfieldID gOptions_mimeFieldID;
jfieldID gOptions_mCancelID;
jfieldID gOptions_bitmapFieldID;

jfieldID gBitmap_nativeBitmapFieldID;
jfieldID gBitmap_layoutBoundsFieldID;
jfieldID gBitmap_ninePatchInsetsFieldID;

#if 0
    #define TRACE_BITMAP(code)  code
#else
    #define TRACE_BITMAP(code)
#endif
jclass gInsetStruct_class;
jmethodID gInsetStruct_constructorMethodID;

using namespace android;

@@ -195,8 +193,7 @@ private:
    const unsigned int mSize;
};

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
        jobject options) {
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {

    int sampleSize = 1;

@@ -331,12 +328,12 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
    }

    jbyteArray ninePatchChunk = NULL;
    if (peeker.fPatch != NULL) {
    if (peeker.mPatch != NULL) {
        if (willScale) {
            scaleNinePatchChunk(peeker.fPatch, scale);
            scaleNinePatchChunk(peeker.mPatch, scale);
        }

        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
        size_t ninePatchArraySize = peeker.mPatch->serializedSize();
        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
        if (ninePatchChunk == NULL) {
            return nullObjectReturn("ninePatchChunk == null");
@@ -347,28 +344,18 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
            return nullObjectReturn("primitive array == null");
        }

        memcpy(array, peeker.fPatch, peeker.fPatchSize);
        memcpy(array, peeker.mPatch, peeker.mPatchSize);
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    }

    jintArray layoutBounds = NULL;
    if (peeker.fLayoutBounds != NULL) {
        layoutBounds = env->NewIntArray(4);
        if (layoutBounds == NULL) {
            return nullObjectReturn("layoutBounds == null");
        }

        jint scaledBounds[4];
        if (willScale) {
            for (int i=0; i<4; i++) {
                scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f);
            }
        } else {
            memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds));
        }
        env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds);
    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.mOutlineFilled, scale);
        if (javaBitmap != NULL) {
            env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
            env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
        }
    }

@@ -409,10 +396,10 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
    }

    if (padding) {
        if (peeker.fPatch != NULL) {
        if (peeker.mPatch != NULL) {
            GraphicsJNI::set_jrect(env, padding,
                    peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
                    peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
        } else {
            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
        }
@@ -446,7 +433,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding

    // now create the java bitmap
    return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
            bitmapCreateFlags, ninePatchChunk, layoutBounds, -1);
            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

// Need to buffer enough input to be able to rewind as much as might be read by a decoder
@@ -598,7 +585,12 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
    jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
    SkASSERT(bitmap_class);
    gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "J");
    gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mOpticalInsets", "[I");
    gBitmap_ninePatchInsetsFieldID = getFieldIDCheck(env, bitmap_class, "mNinePatchInsets",
            "Landroid/graphics/NinePatch$InsetStruct;");

    gInsetStruct_class = (jclass) env->NewGlobalRef(env->FindClass("android/graphics/NinePatch$InsetStruct"));
    gInsetStruct_constructorMethodID = env->GetMethodID(gInsetStruct_class, "<init>", "(IIIIIIIIFZF)V");

    int ret = AndroidRuntime::registerNativeMethods(env,
                                    "android/graphics/BitmapFactory$Options",
                                    gOptionsMethods,
+3 −9
Original line number Diff line number Diff line
@@ -415,7 +415,7 @@ static void assert_premultiplied(const SkBitmap& bitmap, bool isPremultiplied) {
}

jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
        int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density)
        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density)
{
    SkASSERT(bitmap);
    SkASSERT(bitmap->pixelRef());
@@ -429,17 +429,11 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buff
    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
            reinterpret_cast<jlong>(bitmap), buffer,
            bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
            ninepatch, layoutbounds);
            ninePatchChunk, ninePatchInsets);
    hasException(env); // For the side effect of logging.
    return obj;
}

jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
        jbyteArray ninepatch, int density)
{
    return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninepatch, NULL, density);
}

void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap,
        bool isPremultiplied)
{
@@ -720,7 +714,7 @@ int register_android_graphics_Graphics(JNIEnv* env)

    gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
    gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "J");
    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[B[I)V");
    gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V");
    gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V");
    gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I");
    gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder");
+4 −2
Original line number Diff line number Diff line
@@ -76,10 +76,12 @@ public:
        bitmap's SkAlphaType must already be in sync with bitmapCreateFlags.
    */
    static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
            int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1);
            int bitmapCreateFlags, jbyteArray ninePatch, jobject ninePatchInsets, int density = -1);

    static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
            jbyteArray ninepatch, int density = -1);
            jbyteArray ninePatch, int density = -1) {
        return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density);
    }

    /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in
        sync with isPremultiplied
+13 −11
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@
using namespace android;

bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
    if (strcmp("npTc", tag) == 0 && length >= sizeof(Res_png_9patch)) {
    if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) {
        Res_png_9patch* patch = (Res_png_9patch*) data;
        size_t patchSize = patch->serializedSize();
        assert(length == patchSize);
@@ -30,12 +30,9 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
        memcpy(patchNew, patch, patchSize);
        Res_png_9patch::deserialize(patchNew);
        patchNew->fileToDevice();
        free(fPatch);
        fPatch = patchNew;
        fPatchSize = patchSize;
        //printf("9patch: (%d,%d)-(%d,%d)\n",
        //       fPatch.sizeLeft, fPatch.sizeTop,
        //       fPatch.sizeRight, fPatch.sizeBottom);
        free(mPatch);
        mPatch = patchNew;
        mPatchSize = patchSize;

        // now update our host to force index or 32bit config
        // 'cause we don't want 565 predithered, since as a 9patch, we know
@@ -47,10 +44,15 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) {
        table.fPrefFor_8bpc_NoAlpha_src     = SkBitmap::kARGB_8888_Config;
        table.fPrefFor_8bpc_YesAlpha_src    = SkBitmap::kARGB_8888_Config;

        fHost->setPrefConfigTable(table);
    } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) {
        fLayoutBounds = new int[4];
        memcpy(fLayoutBounds, data, sizeof(int) * 4);
        mHost->setPrefConfigTable(table);
    } else if (!strcmp("npLb", tag) && length == sizeof(int32_t) * 4) {
        mHasInsets = true;
        memcpy(&mOpticalInsets, data, sizeof(int32_t) * 4);
    } else if (!strcmp("npOl", tag) && length == 24) { // 4 int32_ts, 1 float, 1 int32_t sized bool
        mHasInsets = true;
        memcpy(&mOutlineInsets, data, sizeof(int32_t) * 4);
        mOutlineRadius = ((const float*)data)[4];
        mOutlineFilled = ((const int32_t*)data)[5] & 0x01;
    }
    return true;    // keep on decoding
}
+21 −13
Original line number Diff line number Diff line
@@ -23,26 +23,34 @@
using namespace android;

class NinePatchPeeker : public SkImageDecoder::Peeker {
    SkImageDecoder* fHost;
public:
    NinePatchPeeker(SkImageDecoder* host) {
private:
    // the host lives longer than we do, so a raw ptr is safe
        fHost = host;
        fPatch = NULL;
        fPatchSize = 0;
        fLayoutBounds = NULL;
    SkImageDecoder* mHost;
public:
    NinePatchPeeker(SkImageDecoder* host)
            : mHost(host)
            , mPatch(NULL)
            , mPatchSize(0)
            , mHasInsets(false)
            , mOutlineRadius(0)
            , mOutlineFilled(false) {
        memset(mOpticalInsets, 0, 4 * sizeof(int32_t));
        memset(mOutlineInsets, 0, 4 * sizeof(int32_t));
    }

    ~NinePatchPeeker() {
        free(fPatch);
        delete fLayoutBounds;
        free(mPatch);
    }

    Res_png_9patch*  fPatch;
    size_t fPatchSize;
    int    *fLayoutBounds;

    virtual bool peek(const char tag[], const void* data, size_t length);

    Res_png_9patch* mPatch;
    size_t mPatchSize;
    bool mHasInsets;
    int32_t mOpticalInsets[4];
    int32_t mOutlineInsets[4];
    float mOutlineRadius;
    bool mOutlineFilled;
};

#endif // NinePatchPeeker_h
Loading