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
This commit is contained in:
Leon Scroggins III
2018-01-14 16:52:17 -05:00
parent 8c9d8f2aec
commit 671cce2605
12 changed files with 455 additions and 42 deletions

View File

@@ -120,6 +120,7 @@ cc_library_shared {
"android_util_jar_StrictJarFile.cpp",
"android_graphics_Canvas.cpp",
"android_graphics_Picture.cpp",
"android/graphics/AnimatedImageDrawable.cpp",
"android/graphics/Bitmap.cpp",
"android/graphics/BitmapFactory.cpp",
"android/graphics/ByteBufferStreamAdaptor.cpp",

View File

@@ -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_Graphics(JNIEnv* env);
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_MaskFilter(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_GraphicBuffer),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),

View File

@@ -0,0 +1,154 @@
/*
* 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));
}

View File

@@ -19,12 +19,11 @@
#include "ByteBufferStreamAdaptor.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "NinePatchPeeker.h"
#include "ImageDecoder.h"
#include "Utils.h"
#include "core_jni_helpers.h"
#include <hwui/Bitmap.h>
#include <hwui/Canvas.h>
#include <SkAndroidCodec.h>
#include <SkEncodedImageFormat.h>
@@ -51,26 +50,6 @@ static jmethodID gCallback_onPartialImageMethodID;
static jmethodID gCanvas_constructorMethodID;
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) {
if (!stream.get()) {
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);
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) {
switch (result) {
case SkCodec::kIncompleteInput:
@@ -90,22 +69,24 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
SkCodec::ResultToString(result));
doThrowIOE(env, msg.c_str());
break;
}
}
return nullptr;
}
// FIXME: Avoid parsing the whole image?
const bool animated = codec->getFrameCount() > 1;
decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
if (!decoder->mCodec.get()) {
doThrowIOE(env, "Could not create AndroidCodec");
return nullptr;
}
const auto& info = decoder->mCodec->getInfo();
const int width = info.width();
const int height = info.height();
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*/,
@@ -176,8 +157,8 @@ static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jby
return native_create(env, std::move(stream));
}
static jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder,
std::unique_ptr<Canvas> canvas, int width, int height) {
jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
int width, int height) {
jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
reinterpret_cast<jlong>(canvas.get()));
if (!jcanvas) {
@@ -340,23 +321,23 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
// Ignore ninepatch when post-processing.
if (!jpostProcess) {
// FIXME: Share more code with BitmapFactory.cpp.
if (decoder->mPeeker.mPatch != nullptr) {
if (decoder->mPeeker->mPatch != nullptr) {
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);
if (ninePatchChunk == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch chunk.");
return nullptr;
}
env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
}
if (decoder->mPeeker.mHasInsets) {
ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
if (decoder->mPeeker->mHasInsets) {
ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
if (ninePatchInsets == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch insets.");
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,
jobject outPadding) {
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) {
@@ -525,7 +506,7 @@ static const JNINativeMethod gImageDecoderMethods[] = {
int register_android_graphics_ImageDecoder(JNIEnv* env) {
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");
gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));

View File

@@ -0,0 +1,55 @@
/*
* 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);

View File

@@ -26,6 +26,7 @@ import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.NinePatchDrawable;
@@ -294,9 +295,10 @@ public final class ImageDecoder implements AutoCloseable {
};
// Fields
private long mNativePtr;
private final int mWidth;
private final int mHeight;
private long mNativePtr;
private final int mWidth;
private final int mHeight;
private final boolean mAnimated;
private int mDesiredWidth;
private int mDesiredHeight;
@@ -322,12 +324,14 @@ public final class ImageDecoder implements AutoCloseable {
* called after decoding to delete native resources.
*/
@SuppressWarnings("unused")
private ImageDecoder(long nativePtr, int width, int height) {
private ImageDecoder(long nativePtr, int width, int height,
boolean animated) {
mNativePtr = nativePtr;
mWidth = width;
mHeight = height;
mDesiredWidth = width;
mDesiredHeight = height;
mAnimated = animated;
mCloseGuard.open("close");
}
@@ -726,6 +730,21 @@ public final class ImageDecoder implements AutoCloseable {
"Drawable!");
}
if (decoder.mAnimated) {
// AnimatedImageDrawable calls postProcessAndRelease only if
// mPostProcess exists.
ImageDecoder postProcessPtr = decoder.mPostProcess == null ?
null : decoder;
Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
postProcessPtr, decoder.mDesiredWidth,
decoder.mDesiredHeight, decoder.mCropRect,
decoder.mInputStream, decoder.mAssetFd);
// d has taken ownership of these objects.
decoder.mInputStream = null;
decoder.mAssetFd = null;
return d;
}
Bitmap bm = decoder.decodeBitmap();
Resources res = src.getResources();
if (res == null) {
@@ -742,7 +761,6 @@ public final class ImageDecoder implements AutoCloseable {
opticalInsets, null);
}
// TODO: Handle animation.
return new BitmapDrawable(res, bm);
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.
*/
package android.graphics.drawable;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.SystemClock;
import libcore.io.IoUtils;
import libcore.util.NativeAllocationRegistry;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Runnable;
/**
* @hide
*/
public class AnimatedImageDrawable extends Drawable implements Animatable {
private final long mNativePtr;
private final InputStream mInputStream;
private final AssetFileDescriptor mAssetFd;
private final int mIntrinsicWidth;
private final int mIntrinsicHeight;
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
invalidateSelf();
}
};
/**
* @hide
* This should only be called by ImageDecoder.
*
* decoder is only non-null if it has a PostProcess
*/
public AnimatedImageDrawable(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect,
InputStream inputStream, AssetFileDescriptor afd)
throws IOException {
mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
mInputStream = inputStream;
mAssetFd = afd;
if (cropRect == null) {
mIntrinsicWidth = width;
mIntrinsicHeight = height;
} else {
mIntrinsicWidth = cropRect.width();
mIntrinsicHeight = cropRect.height();
}
long nativeSize = nNativeByteSize(mNativePtr);
NativeAllocationRegistry registry = new NativeAllocationRegistry(
AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, mNativePtr);
}
@Override
protected void finalize() throws Throwable {
// FIXME: It's a shame that we have *both* a native finalizer and a Java
// one. The native one is necessary to report how much memory is being
// used natively, and this one is necessary to close the input. An
// alternative might be to read the entire stream ahead of time, so we
// can eliminate the Java finalizer.
try {
IoUtils.closeQuietly(mInputStream);
IoUtils.closeQuietly(mAssetFd);
} finally {
super.finalize();
}
}
@Override
public int getIntrinsicWidth() {
return mIntrinsicWidth;
}
@Override
public int getIntrinsicHeight() {
return mIntrinsicHeight;
}
@Override
public void draw(@NonNull Canvas canvas) {
long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
SystemClock.uptimeMillis());
scheduleSelf(mRunnable, nextUpdate);
}
@Override
public void setAlpha(@IntRange(from=0,to=255) int alpha) {
if (alpha < 0 || alpha > 255) {
throw new IllegalArgumentException("Alpha must be between 0 and"
+ " 255! provided " + alpha);
}
nSetAlpha(mNativePtr, alpha);
}
@Override
public int getAlpha() {
return nGetAlpha(mNativePtr);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
nSetColorFilter(mNativePtr, nativeFilter);
}
@Override
public @PixelFormat.Opacity int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
// TODO: Add a Constant State?
// @Override
// public @Nullable ConstantState getConstantState() {}
// Animatable overrides
@Override
public boolean isRunning() {
return nIsRunning(mNativePtr);
}
@Override
public void start() {
nStart(mNativePtr);
}
@Override
public void stop() {
nStop(mNativePtr);
}
private static native long nCreate(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
throws IOException;
private static native long nGetNativeFinalizer();
private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
private static native void nSetAlpha(long nativePtr, int alpha);
private static native int nGetAlpha(long nativePtr);
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
private static native boolean nIsRunning(long nativePtr);
private static native void nStart(long nativePtr);
private static native void nStop(long nativePtr);
private static native long nNativeByteSize(long nativePtr);
}

View File

@@ -495,6 +495,11 @@ void RecordingCanvas::drawNinePatch(Bitmap& bitmap, const android::Res_png_9patc
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
}
void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
const SkPaint*) {
// Unimplemented
}
// Text
void RecordingCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int glyphCount, const SkPaint& paint,
float x, float y, float boundsLeft, float boundsTop,

View File

@@ -183,6 +183,8 @@ public:
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
const SkPaint* paint) override;
// Text
virtual bool drawTextAbsolutePos() const override { return false; }

View File

@@ -23,6 +23,7 @@
#include "hwui/MinikinUtils.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include <SkAnimatedImage.h>
#include <SkCanvasStateUtils.h>
#include <SkColorFilter.h>
#include <SkColorSpaceXformCanvas.h>
@@ -32,6 +33,7 @@
#include <SkGraphics.h>
#include <SkImage.h>
#include <SkImagePriv.h>
#include <SkPicture.h>
#include <SkRSXform.h>
#include <SkShader.h>
#include <SkTemplates.h>
@@ -723,6 +725,20 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa
mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
}
void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
const SkPaint* paint) {
sk_sp<SkPicture> pic(image->newPictureSnapshot());
SkMatrix matrixStorage;
SkMatrix* matrix;
if (left == 0.0f && top == 0.0f) {
matrix = nullptr;
} else {
matrixStorage = SkMatrix::MakeTrans(left, top);
matrix = &matrixStorage;
}
mCanvas->drawPicture(pic.get(), matrix, paint);
}
void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
vectorDrawable->drawStaging(this);
}

View File

@@ -124,6 +124,8 @@ public:
virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
const SkPaint* paint) override;
virtual bool drawTextAbsolutePos() const override { return true; }
virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;

View File

@@ -28,6 +28,7 @@
#include <SkCanvas.h>
#include <SkMatrix.h>
class SkAnimatedImage;
class SkCanvasState;
class SkVertices;
@@ -237,6 +238,9 @@ public:
float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) = 0;
virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
const SkPaint* paint) = 0;
/**
* Specifies if the positions passed to ::drawText are absolute or relative
* to the (x,y) value provided.