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

Commit 671cce26 authored by Leon Scroggins III's avatar Leon Scroggins III
Browse files

Make ImageDecoder return animated Drawables

Bug: 63909536
Bug: 63908092
Test: TODO

If ImageDecoder.decodeDrawable is called with an animated image Source
(currently GIF or WebP), return an object of a new (hidden) Drawable
subclass. The new Drawable animates, and it implements Animatable (TODO:
implement Animatable2) so users have some control over the animation.

In addition to the normal features of Drawable, this new one supports
many of the features of ImageDecoder, including scaling, cropping and
PostProcess, which enables circle masks/rounded corners and other
arbitrary after-effects. It does *not* support decoding directly to a
Hardware Bitmap, since it cycles through frames and reuses the same
bitmap memory. But it could be made to use shared memory (TODO?).

TODO: Use a better number for the native allocation registry
TODO: Use the RenderThread to drive the animation, and remove decoding
on the UI thread.
TODO: Add support for modifying the loop count

Android.bp:
- build new AnimatedImageDrawable.cpp

AndroidRuntime.cpp:
- register new native methods

AnimatedImageDrawable.java
AnimatedImageDrawable.cpp:
- new Drawable that handles animated images

Canvas.h, SkiaCanvas.h/.cpp
- New virtual method and implementation for drawing SkAnimatedImages

RecordingCanvas.h/.cpp
- Stub implementation of drawing SkAnimatedImages

ImageDecoder.h/cpp
- Allow code sharing with AnimatedImageDrawable.cpp
  - postProcess
  - access the ImageDecoder struct

Depends on https://skia-review.googlesource.com/c/skia/+/94660 in Skia.

Change-Id: Ie2ec98d9c52deda4d439c6ef8e5dea2861bb93a3
parent 8c9d8f2a
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -120,6 +120,7 @@ cc_library_shared {
        "android_util_jar_StrictJarFile.cpp",
        "android_util_jar_StrictJarFile.cpp",
        "android_graphics_Canvas.cpp",
        "android_graphics_Canvas.cpp",
        "android_graphics_Picture.cpp",
        "android_graphics_Picture.cpp",
        "android/graphics/AnimatedImageDrawable.cpp",
        "android/graphics/Bitmap.cpp",
        "android/graphics/Bitmap.cpp",
        "android/graphics/BitmapFactory.cpp",
        "android/graphics/BitmapFactory.cpp",
        "android/graphics/ByteBufferStreamAdaptor.cpp",
        "android/graphics/ByteBufferStreamAdaptor.cpp",
+2 −0
Original line number Original line Diff line number Diff line
@@ -63,6 +63,7 @@ extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
extern int register_android_graphics_Movie(JNIEnv* env);
@@ -1397,6 +1398,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_graphics_FontFamily),
    REG_JNI(register_android_graphics_FontFamily),
    REG_JNI(register_android_graphics_GraphicBuffer),
    REG_JNI(register_android_graphics_GraphicBuffer),
    REG_JNI(register_android_graphics_ImageDecoder),
    REG_JNI(register_android_graphics_ImageDecoder),
    REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
    REG_JNI(register_android_graphics_Interpolator),
    REG_JNI(register_android_graphics_Interpolator),
    REG_JNI(register_android_graphics_MaskFilter),
    REG_JNI(register_android_graphics_MaskFilter),
    REG_JNI(register_android_graphics_Matrix),
    REG_JNI(register_android_graphics_Matrix),
+154 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "GraphicsJNI.h"
#include "ImageDecoder.h"
#include "core_jni_helpers.h"

#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkAnimatedImage.h>
#include <SkColorFilter.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>

using namespace android;

struct AnimatedImageDrawable {
    sk_sp<SkAnimatedImage> mDrawable;
    SkPaint                mPaint;
};

// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
                                           jlong nativeImageDecoder, jobject jpostProcess,
                                           jint width, jint height, jobject jsubset) {
    if (nativeImageDecoder == 0) {
        doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!");
        return 0;
    }

    auto* imageDecoder = reinterpret_cast<ImageDecoder*>(nativeImageDecoder);
    auto info = imageDecoder->mCodec->getInfo();
    const SkISize scaledSize = SkISize::Make(width, height);
    SkIRect subset;
    if (jsubset) {
        GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
    } else {
        subset = SkIRect::MakeWH(width, height);
    }

    sk_sp<SkPicture> picture;
    if (jpostProcess) {
        SkRect bounds = SkRect::MakeWH(subset.width(), subset.height());

        SkPictureRecorder recorder;
        SkCanvas* skcanvas = recorder.beginRecording(bounds);
        std::unique_ptr<Canvas> canvas(Canvas::create_canvas(skcanvas));
        postProcessAndRelease(env, jpostProcess, std::move(canvas), bounds.width(),
                              bounds.height());
        if (env->ExceptionCheck()) {
            return 0;
        }
        picture = recorder.finishRecordingAsPicture();
    }

    std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
    drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
                scaledSize, subset, std::move(picture));
    if (!drawable->mDrawable) {
        doThrowIOE(env, "Failed to create drawable");
        return 0;
    }
    drawable->mDrawable->start();

    return reinterpret_cast<jlong>(drawable.release());
}

static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
    delete drawable;
}

static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct));
}

static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                         jlong canvasPtr, jlong msecs) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    double timeToNextUpdate = drawable->mDrawable->update(msecs);
    auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
    canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
    return (jlong) timeToNextUpdate;
}

static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                            jint alpha) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    drawable->mPaint.setAlpha(alpha);
}

static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    return drawable->mPaint.getAlpha();
}

static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                                  jlong nativeFilter) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
    drawable->mPaint.setColorFilter(sk_ref_sp(filter));
}

static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    return drawable->mDrawable->isRunning();
}

static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    drawable->mDrawable->start();
}

static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    drawable->mDrawable->stop();
}

static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    // FIXME: Report the size of the internal SkBitmap etc.
    return sizeof(drawable);
}

static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
    { "nCreate",             "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
    { "nGetNativeFinalizer", "()J",                                                          (void*) AnimatedImageDrawable_nGetNativeFinalizer },
    { "nDraw",               "(JJJ)J",                                                       (void*) AnimatedImageDrawable_nDraw },
    { "nSetAlpha",           "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetAlpha },
    { "nGetAlpha",           "(J)I",                                                         (void*) AnimatedImageDrawable_nGetAlpha },
    { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
    { "nIsRunning",          "(J)Z",                                                         (void*) AnimatedImageDrawable_nIsRunning },
    { "nStart",              "(J)V",                                                         (void*) AnimatedImageDrawable_nStart },
    { "nStop",               "(J)V",                                                         (void*) AnimatedImageDrawable_nStop },
    { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
};

int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
    return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
            gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
}
+18 −37
Original line number Original line Diff line number Diff line
@@ -19,12 +19,11 @@
#include "ByteBufferStreamAdaptor.h"
#include "ByteBufferStreamAdaptor.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "GraphicsJNI.h"
#include "NinePatchPeeker.h"
#include "ImageDecoder.h"
#include "Utils.h"
#include "Utils.h"
#include "core_jni_helpers.h"
#include "core_jni_helpers.h"


#include <hwui/Bitmap.h>
#include <hwui/Bitmap.h>
#include <hwui/Canvas.h>


#include <SkAndroidCodec.h>
#include <SkAndroidCodec.h>
#include <SkEncodedImageFormat.h>
#include <SkEncodedImageFormat.h>
@@ -51,26 +50,6 @@ static jmethodID gCallback_onPartialImageMethodID;
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
static jmethodID gCanvas_releaseMethodID;


struct ImageDecoder {
    // These need to stay in sync with ImageDecoder.java's Allocator constants.
    enum Allocator {
        kDefault_Allocator      = 0,
        kSoftware_Allocator     = 1,
        kSharedMemory_Allocator = 2,
        kHardware_Allocator     = 3,
    };

    // These need to stay in sync with PixelFormat.java's Format constants.
    enum PixelFormat {
        kUnknown     =  0,
        kTranslucent = -3,
        kOpaque      = -1,
    };

    NinePatchPeeker mPeeker;
    std::unique_ptr<SkAndroidCodec> mCodec;
};

static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
    if (!stream.get()) {
    if (!stream.get()) {
        doThrowIOE(env, "Failed to create a stream");
        doThrowIOE(env, "Failed to create a stream");
@@ -78,7 +57,7 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
    }
    }
    std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
    std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
    SkCodec::Result result;
    SkCodec::Result result;
    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, &decoder->mPeeker);
    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get());
    if (!codec) {
    if (!codec) {
        switch (result) {
        switch (result) {
            case SkCodec::kIncompleteInput:
            case SkCodec::kIncompleteInput:
@@ -90,22 +69,24 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
                           SkCodec::ResultToString(result));
                           SkCodec::ResultToString(result));
                doThrowIOE(env, msg.c_str());
                doThrowIOE(env, msg.c_str());
                break;
                break;
        }


        }
        return nullptr;
        return nullptr;
    }
    }


    // FIXME: Avoid parsing the whole image?
    const bool animated = codec->getFrameCount() > 1;
    decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
    decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
    if (!decoder->mCodec.get()) {
    if (!decoder->mCodec.get()) {
        doThrowIOE(env, "Could not create AndroidCodec");
        doThrowIOE(env, "Could not create AndroidCodec");
        return nullptr;
        return nullptr;
    }
    }

    const auto& info = decoder->mCodec->getInfo();
    const auto& info = decoder->mCodec->getInfo();
    const int width = info.width();
    const int width = info.width();
    const int height = info.height();
    const int height = info.height();
    return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
    return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
                          reinterpret_cast<jlong>(decoder.release()), width, height);
                          reinterpret_cast<jlong>(decoder.release()), width, height,
                          animated);
}
}


static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
@@ -176,8 +157,8 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby
    return native_create(env, std::move(stream));
    return native_create(env, std::move(stream));
}
}


static jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder,
jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
                                  std::unique_ptr<Canvas> canvas, int width, int height) {
                           int width, int height) {
    jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
    jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
                                     reinterpret_cast<jlong>(canvas.get()));
                                     reinterpret_cast<jlong>(canvas.get()));
    if (!jcanvas) {
    if (!jcanvas) {
@@ -340,23 +321,23 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
    // Ignore ninepatch when post-processing.
    // Ignore ninepatch when post-processing.
    if (!jpostProcess) {
    if (!jpostProcess) {
        // FIXME: Share more code with BitmapFactory.cpp.
        // FIXME: Share more code with BitmapFactory.cpp.
        if (decoder->mPeeker.mPatch != nullptr) {
        if (decoder->mPeeker->mPatch != nullptr) {
            if (scale) {
            if (scale) {
                decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
                decoder->mPeeker->scale(scaleX, scaleY, desiredWidth, desiredHeight);
            }
            }
            size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
            size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize();
            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
            if (ninePatchChunk == nullptr) {
            if (ninePatchChunk == nullptr) {
                doThrowOOME(env, "Failed to allocate nine patch chunk.");
                doThrowOOME(env, "Failed to allocate nine patch chunk.");
                return nullptr;
                return nullptr;
            }
            }


            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
                                    reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
                                    reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
        }
        }


        if (decoder->mPeeker.mHasInsets) {
        if (decoder->mPeeker->mHasInsets) {
            ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
            ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
            if (ninePatchInsets == nullptr) {
            if (ninePatchInsets == nullptr) {
                doThrowOOME(env, "Failed to allocate nine patch insets.");
                doThrowOOME(env, "Failed to allocate nine patch insets.");
                return nullptr;
                return nullptr;
@@ -497,7 +478,7 @@ static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlon
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                     jobject outPadding) {
                                     jobject outPadding) {
    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
    decoder->mPeeker.getPadding(env, outPadding);
    decoder->mPeeker->getPadding(env, outPadding);
}
}


static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
@@ -525,7 +506,7 @@ static const JNINativeMethod gImageDecoderMethods[] = {


int register_android_graphics_ImageDecoder(JNIEnv* env) {
int register_android_graphics_ImageDecoder(JNIEnv* env) {
    gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
    gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZ)V");
    gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I");
    gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I");


    gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
    gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
+55 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "NinePatchPeeker.h"

#include <hwui/Canvas.h>

#include <jni.h>

class SkAndroidCodec;

using namespace android;

struct ImageDecoder {
    // These need to stay in sync with ImageDecoder.java's Allocator constants.
    enum Allocator {
        kDefault_Allocator      = 0,
        kSoftware_Allocator     = 1,
        kSharedMemory_Allocator = 2,
        kHardware_Allocator     = 3,
    };

    // These need to stay in sync with PixelFormat.java's Format constants.
    enum PixelFormat {
        kUnknown     =  0,
        kTranslucent = -3,
        kOpaque      = -1,
    };

    std::unique_ptr<SkAndroidCodec> mCodec;
    sk_sp<NinePatchPeeker> mPeeker;

    ImageDecoder()
        :mPeeker(new NinePatchPeeker)
    {}
};

// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then
// releases the Canvas.
// Caller needs to check for exceptions.
jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
                           int width, int height);
Loading