diff options
author | 2016-01-07 13:49:26 -0800 | |
---|---|---|
committer | 2016-02-02 15:04:01 -0800 | |
commit | f276acd98457bcaabc9e79a17a736b3b484f005e (patch) | |
tree | b9efee10194c520b09a4813f7b8074be9a2f99c8 | |
parent | 650e3b70e4aa2fa9acf2f9c6ce211c4b46862c15 (diff) |
VectorDrawable native rendering - Step 4 of MANY
This CL runs VectorDrawable animation on RenderThread. The changes in this CL
include:
- Convert all the animators in AnimatorSet for AVD into a set of RenderNodeAnimators.
- Hook up the new animators with RenderThread
- Add drawOp in RecordingCanvas for drawing VD so that during the animation
on RenderThread, all the property changes on VD can be reflected on the screen.
TODO:
- Implement reverse and reset for AVD.
Change-Id: I2df1d754f2db0ad098d9c15dde4bb2bdfafc2315
29 files changed, 1601 insertions, 243 deletions
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 1ab55dd7c1d9..980329fe31c8 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -1030,6 +1030,20 @@ public final class AnimatorSet extends Animator { } } + /** + * @hide + * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order + * if defined (i.e. sequential or together), then we can use the flag instead of calculate + * dynamically. + * @return whether all the animators in the set are supposed to play together + */ + public boolean shouldPlayTogether() { + updateAnimatorsDuration(); + createDependencyGraph(); + // All the child nodes are set out to play right after the delay animation + return mRootNode.mChildNodes.size() == mNodes.size() - 1; + } + @Override public long getTotalDuration() { updateAnimatorsDuration(); diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java index 2a47b68a41b0..8230ac5bbfc5 100644 --- a/core/java/android/animation/PathKeyframes.java +++ b/core/java/android/animation/PathKeyframes.java @@ -231,7 +231,7 @@ class PathKeyframes implements Keyframes { } } - private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { + abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { @Override public Class getType() { return Integer.class; @@ -243,7 +243,7 @@ class PathKeyframes implements Keyframes { } } - private abstract static class FloatKeyframesBase extends SimpleKeyframes + abstract static class FloatKeyframesBase extends SimpleKeyframes implements FloatKeyframes { @Override public Class getType() { diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index e993cca9e325..6ba5b968dfe7 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -21,6 +21,7 @@ import android.graphics.PointF; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Log; +import android.util.PathParser; import android.util.Property; import java.lang.reflect.InvocationTargetException; @@ -1046,6 +1047,43 @@ public class PropertyValuesHolder implements Cloneable { return mAnimatedValue; } + /** + * PropertyValuesHolder is Animators use to hold internal animation related data. + * Therefore, in order to replicate the animation behavior, we need to get data out of + * PropertyValuesHolder. + * @hide + */ + public void getPropertyValues(PropertyValues values) { + init(); + values.propertyName = mPropertyName; + values.type = mValueType; + values.startValue = mKeyframes.getValue(0); + if (values.startValue instanceof PathParser.PathData) { + // PathData evaluator returns the same mutable PathData object when query fraction, + // so we have to make a copy here. + values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue); + } + values.endValue = mKeyframes.getValue(1); + if (values.endValue instanceof PathParser.PathData) { + // PathData evaluator returns the same mutable PathData object when query fraction, + // so we have to make a copy here. + values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue); + } + // TODO: We need a better way to get data out of keyframes. + if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase + || mKeyframes instanceof PathKeyframes.IntKeyframesBase) { + // property values will animate based on external data source (e.g. Path) + values.dataSource = new PropertyValues.DataSource() { + @Override + public Object getValueAtFraction(float fraction) { + return mKeyframes.getValue(fraction); + } + }; + } else { + values.dataSource = null; + } + } + @Override public String toString() { return mPropertyName + ": " + mKeyframes.toString(); @@ -1601,6 +1639,24 @@ public class PropertyValuesHolder implements Cloneable { } }; + /** + * @hide + */ + public static class PropertyValues { + public String propertyName; + public Class type; + public Object startValue; + public Object endValue; + public DataSource dataSource = null; + public interface DataSource { + Object getValueAtFraction(float fraction); + } + public String toString() { + return ("property name: " + propertyName + ", type: " + type + ", startValue: " + + startValue.toString() + ", endValue: " + endValue.toString()); + } + } + native static private long nGetIntMethod(Class targetClass, String methodName); native static private long nGetFloatMethod(Class targetClass, String methodName); native static private long nGetMultipleIntMethod(Class targetClass, String methodName, diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index 78d5bcd90624..29a72fdf2288 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -104,6 +104,7 @@ public class PathParser { } super.finalize(); } + } /** diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 88bffb5827aa..2aace0f30139 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -22,6 +22,7 @@ import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; /** * <p>A display list records a series of graphics related operations and can replay @@ -771,6 +772,14 @@ public class RenderNode { mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); } + public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) { + if (mOwningView == null || mOwningView.mAttachInfo == null) { + throw new IllegalStateException("Cannot start this animator on a detached view!"); + } + nAddAnimator(mNativeRenderNode, animatorSet.getAnimatorNativePtr()); + mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); + } + public void endAllAnimators() { nEndAllAnimators(mNativeRenderNode); } diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java new file mode 100644 index 000000000000..ba592d29fa3d --- /dev/null +++ b/core/java/android/view/RenderNodeAnimatorSetHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 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.view; + +import android.animation.TimeInterpolator; +import com.android.internal.view.animation.FallbackLUTInterpolator; +import com.android.internal.view.animation.NativeInterpolatorFactory; +import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; + +/** + * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet + * that are internal or package private to android.view package. + * + * @hide + */ +public class RenderNodeAnimatorSetHelper { + + public static RenderNode getTarget(DisplayListCanvas recordingCanvas) { + return recordingCanvas.mNode; + } + + public static long createNativeInterpolator(TimeInterpolator interpolator, long + duration) { + if (interpolator == null) { + // create LinearInterpolator + return NativeInterpolatorFactoryHelper.createLinearInterpolator(); + } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) { + return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator(); + } else { + return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration); + } + } + +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 8c4683d30baa..f2ef8986250d 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -51,6 +51,7 @@ LOCAL_SRC_FILES:= \ android_database_SQLiteConnection.cpp \ android_database_SQLiteGlobal.cpp \ android_database_SQLiteDebug.cpp \ + android_graphics_drawable_AnimatedVectorDrawable.cpp \ android_graphics_drawable_VectorDrawable.cpp \ android_view_DisplayEventReceiver.cpp \ android_view_DisplayListCanvas.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2e45f8c4d9c0..223fc1af46d5 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -131,6 +131,7 @@ extern int register_android_graphics_Rasterizer(JNIEnv* env); extern int register_android_graphics_Region(JNIEnv* env); extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_Xfermode(JNIEnv* env); +extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); @@ -1321,6 +1322,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Typeface), REG_JNI(register_android_graphics_Xfermode), REG_JNI(register_android_graphics_YuvImage), + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), REG_JNI(register_android_graphics_drawable_VectorDrawable), REG_JNI(register_android_graphics_pdf_PdfDocument), REG_JNI(register_android_graphics_pdf_PdfEditor), diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp new file mode 100644 index 000000000000..7a3c598e0aed --- /dev/null +++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 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. + */ +#define LOG_TAG "OpenGLRenderer" + +#include "jni.h" +#include "GraphicsJNI.h" +#include "core_jni_helpers.h" +#include "log/log.h" + +#include "Animator.h" +#include "Interpolator.h" +#include "PropertyValuesAnimatorSet.h" +#include "PropertyValuesHolder.h" +#include "VectorDrawable.h" + +namespace android { +using namespace uirenderer; +using namespace VectorDrawable; + +static struct { + jclass clazz; + jmethodID callOnFinished; +} gVectorDrawableAnimatorClassInfo; + +static JNIEnv* getEnv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return 0; + } + return env; +} + +static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) { + class AnimationListenerBridge : public AnimationListener { + public: + AnimationListenerBridge(JNIEnv* env, jobject finishListener) { + mFinishListener = env->NewGlobalRef(finishListener); + env->GetJavaVM(&mJvm); + } + + virtual ~AnimationListenerBridge() { + if (mFinishListener) { + onAnimationFinished(NULL); + } + } + + virtual void onAnimationFinished(BaseRenderNodeAnimator*) { + LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?"); + JNIEnv* env = getEnv(mJvm); + env->CallStaticVoidMethod( + gVectorDrawableAnimatorClassInfo.clazz, + gVectorDrawableAnimatorClassInfo.callOnFinished, + mFinishListener); + releaseJavaObject(); + } + + private: + void releaseJavaObject() { + JNIEnv* env = getEnv(mJvm); + env->DeleteGlobalRef(mFinishListener); + mFinishListener = NULL; + } + + JavaVM* mJvm; + jobject mFinishListener; + }; + return new AnimationListenerBridge(env, finishListener); +} + +static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr, + jlong interpolatorPtr, jlong startDelay, jlong duration, jint repeatCount) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr); + Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr); + set->addPropertyAnimator(holder, interpolator, startDelay, duration, repeatCount); +} + +static jlong createAnimatorSet(JNIEnv*, jobject) { + PropertyValuesAnimatorSet* animatorSet = new PropertyValuesAnimatorSet(); + return reinterpret_cast<jlong>(animatorSet); +} + +static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + jfloat startValue, jfloat endValue) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr); + GroupPropertyValuesHolder* newHolder = new GroupPropertyValuesHolder(group, propertyId, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathDataPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jlong startValuePtr, + jlong endValuePtr) { + VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(nativePtr); + PathData* startData = reinterpret_cast<PathData*>(startValuePtr); + PathData* endData = reinterpret_cast<PathData*>(endValuePtr); + PathDataPropertyValuesHolder* newHolder = new PathDataPropertyValuesHolder(path, + startData, endData); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathColorPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + int startValue, jint endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathColorPropertyValuesHolder* newHolder = new FullPathColorPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + float startValue, jfloat endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathPropertyValuesHolder* newHolder = new FullPathPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createRootAlphaPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jfloat startValue, + float endValue) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(nativePtr); + RootAlphaPropertyValuesHolder* newHolder = new RootAlphaPropertyValuesHolder(tree, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} +static void setPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr, + jfloatArray srcData, jint length) { + + jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr); + PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr); + holder->setPropertyDataSource(propertyData, length); + env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT); +} +static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + // TODO: keep a ref count in finish listener + AnimationListener* listener = createAnimationListener(env, finishListener); + set->start(listener); +} + +static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) { + // TODO: implement reverse +} + +static void end(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->end(); +} + +static void reset(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->reset(); +} + +static const JNINativeMethod gMethods[] = { + {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet}, + {"nAddAnimator", "(JJJJJI)V", (void*)addAnimator}, + {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder}, + {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder}, + {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder}, + {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder}, + {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder}, + {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData}, + {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start}, + {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse}, + {"nEnd", "!(J)V", (void*)end}, + {"nReset", "!(J)V", (void*)reset}, +}; + +const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator"; +int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env) { + gVectorDrawableAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName); + gVectorDrawableAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env, + gVectorDrawableAnimatorClassInfo.clazz); + + gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie( + env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished", + "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V"); + return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp index 563ec8bd9834..e88287644555 100644 --- a/core/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp @@ -32,11 +32,6 @@ static jlong createTree(JNIEnv*, jobject, jlong groupPtr) { return reinterpret_cast<jlong>(tree); } -static void deleteTree(JNIEnv*, jobject, jlong treePtr) { - VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); - delete tree; -} - static void setTreeViewportSize(JNIEnv*, jobject, jlong treePtr, jfloat viewportWidth, jfloat viewportHeight) { VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); @@ -333,7 +328,6 @@ static void setTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPa static const JNINativeMethod gMethods[] = { {"nCreateRenderer", "!(J)J", (void*)createTree}, - {"nDestroyRenderer", "!(J)V", (void*)deleteTree}, {"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize}, {"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha}, {"nGetRootAlpha", "!(J)F", (void*)getRootAlpha}, diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 1857345968fd..0a3e27e94c06 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -19,6 +19,10 @@ import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.Animator.AnimatorListener; +import android.animation.PropertyValuesHolder; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ObjectAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; @@ -34,14 +38,23 @@ import android.graphics.Rect; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; +import android.util.LongArray; +import android.util.PathParser; +import android.util.TimeUtils; +import android.view.Choreographer; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.view.RenderNodeAnimatorSetHelper; import android.view.View; import com.android.internal.R; +import com.android.internal.util.VirtualRefBasePtr; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -138,7 +151,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; /** Local, mutable animator set. */ - private final AnimatorSet mAnimatorSet = new AnimatorSet(); + private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator(); /** * The resources against which this drawable was created. Used to attempt @@ -200,6 +213,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @Override public void draw(Canvas canvas) { + if (canvas.isHardwareAccelerated()) { + mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas); + } mAnimatedVectorState.mVectorDrawable.draw(canvas); if (isStarted()) { invalidateSelf(); @@ -582,9 +598,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { * Resets the AnimatedVectorDrawable to the start state as specified in the animators. */ public void reset() { - // TODO: Use reverse or seek to implement reset, when AnimatorSet supports them. - start(); - mAnimatorSet.cancel(); + mAnimatorSet.reset(); + invalidateSelf(); } @Override @@ -603,8 +618,12 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @NonNull private void ensureAnimatorSet() { if (!mHasAnimatorSet) { - mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes); + // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly + // with a list of LocalAnimators. + AnimatorSet set = new AnimatorSet(); + mAnimatedVectorState.prepareLocalAnimators(set, mRes); mHasAnimatorSet = true; + mAnimatorSet.initWithAnimatorSet(set); mRes = null; } } @@ -694,13 +713,13 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } }; } - mAnimatorSet.addListener(mAnimatorListener); + mAnimatorSet.setListener(mAnimatorListener); } // A helper function to clean up the animator listener in the mAnimatorSet. private void removeAnimatorSetListener() { if (mAnimatorListener != null) { - mAnimatorSet.removeListener(mAnimatorListener); + mAnimatorSet.removeListener(); mAnimatorListener = null; } } @@ -730,4 +749,406 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { mAnimationCallbacks.clear(); } -}
\ No newline at end of file + /** + * @hide + */ + public static class VectorDrawableAnimator { + private AnimatorListener mListener = null; + private final LongArray mStartDelays = new LongArray(); + private PropertyValuesHolder.PropertyValues mTmpValues = + new PropertyValuesHolder.PropertyValues(); + private long mSetPtr = 0; + private boolean mContainsSequentialAnimators = false; + private boolean mStarted = false; + private boolean mInitialized = false; + private boolean mAnimationPending = false; + private boolean mIsReversible = false; + // TODO: Consider using NativeAllocationRegistery to track native allocation + private final VirtualRefBasePtr mSetRefBasePtr; + private WeakReference<RenderNode> mTarget = null; + private WeakReference<RenderNode> mLastSeenTarget = null; + + + VectorDrawableAnimator() { + mSetPtr = nCreateAnimatorSet(); + // Increment ref count on native AnimatorSet, so it doesn't get released before Java + // side is done using it. + mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); + } + + private void initWithAnimatorSet(AnimatorSet set) { + if (mInitialized) { + // Already initialized + throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + + "re-initialized"); + } + parseAnimatorSet(set, 0); + mInitialized = true; + + // Check reversible. + if (mContainsSequentialAnimators) { + mIsReversible = false; + } else { + // Check if there's any start delay set on child + for (int i = 0; i < mStartDelays.size(); i++) { + if (mStartDelays.get(i) > 0) { + mIsReversible = false; + return; + } + } + } + mIsReversible = true; + } + + private void parseAnimatorSet(AnimatorSet set, long startTime) { + ArrayList<Animator> animators = set.getChildAnimations(); + + boolean playTogether = set.shouldPlayTogether(); + // Convert AnimatorSet to VectorDrawableAnimator + for (int i = 0; i < animators.size(); i++) { + Animator animator = animators.get(i); + // Here we only support ObjectAnimator + if (animator instanceof AnimatorSet) { + parseAnimatorSet((AnimatorSet) animator, startTime); + } else if (animator instanceof ObjectAnimator) { + createRTAnimator((ObjectAnimator) animator, startTime); + } // ignore ValueAnimators and others because they don't directly modify VD + // therefore will be useless to AVD. + + if (!playTogether) { + // Assume not play together means play sequentially + startTime += animator.getTotalDuration(); + mContainsSequentialAnimators = true; + } + } + } + + // TODO: This method reads animation data from already parsed Animators. We need to move + // this step further up the chain in the parser to avoid the detour. + private void createRTAnimator(ObjectAnimator animator, long startTime) { + PropertyValuesHolder[] values = animator.getValues(); + Object target = animator.getTarget(); + if (target instanceof VectorDrawable.VGroup) { + createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, + startTime); + } else if (target instanceof VectorDrawable.VPath) { + for (int i = 0; i < values.length; i++) { + values[i].getPropertyValues(mTmpValues); + if (mTmpValues.endValue instanceof PathParser.PathData && + mTmpValues.propertyName.equals("pathData")) { + createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, + startTime); + } else if (target instanceof VectorDrawable.VFullPath) { + createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, + startTime); + } else { + throw new IllegalArgumentException("ClipPath only supports PathData " + + "property"); + } + + } + } else if (target instanceof VectorDrawable.VectorDrawableState) { + createRTAnimatorForRootGroup(values, animator, + (VectorDrawable.VectorDrawableState) target, startTime); + } else { + // Should never get here + throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + + "or ConstantState, " + target.getClass() + " is not supported"); + } + } + + private void createRTAnimatorForGroup(PropertyValuesHolder[] values, + ObjectAnimator animator, VectorDrawable.VGroup target, + long startTime) { + + long nativePtr = target.getNativePtr(); + int propertyId; + for (int i = 0; i < values.length; i++) { + // TODO: We need to support the rare case in AVD where no start value is provided + values[i].getPropertyValues(mTmpValues); + propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); + if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.e(LOGTAG, "Unsupported type: " + + mTmpValues.type + ". Only float value is supported for Groups."); + } + continue; + } + if (propertyId < 0) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.e(LOGTAG, "Unsupported property: " + + mTmpValues.propertyName + " for Vector Drawable Group"); + } + continue; + } + long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, + (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); + if (mTmpValues.dataSource != null) { + float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator + .getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } + createNativeChildAnimator(propertyPtr, startTime, animator); + } + } + private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, + long startTime) { + + long nativePtr = target.getNativePtr(); + long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) + .getNativePtr(); + long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) + .getNativePtr(); + long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, + endPathDataPtr); + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + private void createRTAnimatorForFullPath(ObjectAnimator animator, + VectorDrawable.VFullPath target, long startTime) { + + int propertyId = target.getPropertyIndex(mTmpValues.propertyName); + long propertyPtr; + long nativePtr = target.getNativePtr(); + if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { + if (propertyId < 0) { + throw new IllegalArgumentException("Property: " + mTmpValues + .propertyName + " is not supported for FullPath"); + } + propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, + (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); + + } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { + propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, + (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); + } else { + throw new UnsupportedOperationException("Unsupported type: " + + mTmpValues.type + ". Only float, int or PathData value is " + + "supported for Paths."); + } + if (mTmpValues.dataSource != null) { + float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator + .getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, + ObjectAnimator animator, VectorDrawable.VectorDrawableState target, + long startTime) { + long nativePtr = target.getNativeRenderer(); + if (!animator.getPropertyName().equals("alpha")) { + throw new UnsupportedOperationException("Only alpha is supported for root " + + "group"); + } + Float startValue = null; + Float endValue = null; + for (int i = 0; i < values.length; i++) { + values[i].getPropertyValues(mTmpValues); + if (mTmpValues.propertyName.equals("alpha")) { + startValue = (Float) mTmpValues.startValue; + endValue = (Float) mTmpValues.endValue; + break; + } + } + if (startValue == null && endValue == null) { + throw new UnsupportedOperationException("No alpha values are specified"); + } + long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + // These are the data points that define the value of the animating properties. + // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] + // a point on the path corresponds to the values of translateX and translateY. + // TODO: (Optimization) We should pass the path down in native and chop it into segments + // in native. + private static float[] createDataPoints( + PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { + long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); + int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); + int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); + float values[] = new float[numAnimFrames]; + float lastFrame = numAnimFrames - 1; + for (int i = 0; i < numAnimFrames; i++) { + float fraction = i / lastFrame; + values[i] = (Float) dataSource.getValueAtFraction(fraction); + } + return values; + } + + private void createNativeChildAnimator(long propertyPtr, long extraDelay, + ObjectAnimator animator) { + long duration = animator.getDuration(); + int repeatCount = animator.getRepeatCount(); + long startDelay = extraDelay + animator.getStartDelay(); + TimeInterpolator interpolator = animator.getInterpolator(); + long nativeInterpolator = + RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); + + startDelay *= ValueAnimator.getDurationScale(); + duration *= ValueAnimator.getDurationScale(); + + mStartDelays.add(startDelay); + nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, + repeatCount); + } + + /** + * Holds a weak reference to the target that was last seen (through the DisplayListCanvas + * in the last draw call), so that when animator set needs to start, we can add the animator + * to the last seen RenderNode target and start right away. + */ + protected void recordLastSeenTarget(DisplayListCanvas canvas) { + if (mAnimationPending) { + mLastSeenTarget = new WeakReference<RenderNode>( + RenderNodeAnimatorSetHelper.getTarget(canvas)); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Target is set in the next frame"); + } + mAnimationPending = false; + start(); + } else { + mLastSeenTarget = new WeakReference<RenderNode>( + RenderNodeAnimatorSetHelper.getTarget(canvas)); + } + + } + + private boolean setTarget(RenderNode node) { + if (mTarget != null && mTarget.get() != null) { + // TODO: Maybe we want to support target change. + throw new IllegalStateException("Target already set!"); + } + + node.addAnimator(this); + mTarget = new WeakReference<RenderNode>(node); + return true; + } + + private boolean useLastSeenTarget() { + if (mLastSeenTarget != null && mLastSeenTarget.get() != null) { + setTarget(mLastSeenTarget.get()); + return true; + } + return false; + } + + public void start() { + if (!mInitialized) { + return; + } + + if (mStarted) { + return; + } + + if (!useLastSeenTarget()) { + mAnimationPending = true; + return; + } + + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); + } + + nStart(mSetPtr, this); + if (mListener != null) { + mListener.onAnimationStart(null); + } + mStarted = true; + } + + public void end() { + if (mInitialized && mStarted) { + nEnd(mSetPtr); + onAnimationEnd(); + } + } + + void reset() { + if (!mInitialized) { + return; + } + // TODO: Need to implement reset. + Log.w(LOGTAG, "Reset is yet to be implemented"); + nReset(mSetPtr); + } + + // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential + // animators or when the animator set has a start delay + void reverse() { + if (!mIsReversible) { + return; + } + // TODO: Need to support reverse (non-public API) + Log.w(LOGTAG, "Reverse is yet to be implemented"); + nReverse(mSetPtr, this); + } + + public long getAnimatorNativePtr() { + return mSetPtr; + } + + boolean canReverse() { + return mIsReversible; + } + + boolean isStarted() { + return mStarted; + } + + boolean isRunning() { + if (!mInitialized) { + return false; + } + return mStarted; + } + + void setListener(AnimatorListener listener) { + mListener = listener; + } + + void removeListener() { + mListener = null; + } + + private void onAnimationEnd() { + mStarted = false; + if (mListener != null) { + mListener.onAnimationEnd(null); + } + mTarget = null; + } + + // onFinished: should be called from native + private static void callOnFinished(VectorDrawableAnimator set) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "on finished called from native"); + } + set.onAnimationEnd(); + } + } + + private static native long nCreateAnimatorSet(); + private static native void nAddAnimator(long setPtr, long propertyValuesHolder, + long nativeInterpolator, long startDelay, long duration, int repeatCount); + + private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue); + + private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, + long endValuePtr); + private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, + int startValue, int endValue); + private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue); + private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, + float endValue); + private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); + private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set); + private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set); + private static native void nEnd(long animatorSetPtr); + private static native void nReset(long animatorSetPtr); +} diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 1fc1b83f7a6c..f4bbc8c43d08 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -39,6 +39,7 @@ import android.util.PathParser; import android.util.Xml; import com.android.internal.R; +import com.android.internal.util.VirtualRefBasePtr; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -47,6 +48,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; import java.util.Stack; /** @@ -522,13 +524,13 @@ public class VectorDrawable extends Drawable { public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererPtr != 0) { + if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) { // This VD has been used to display other VD resource content, clean up. mVectorState.mRootGroup = new VGroup(); - if (mVectorState.mNativeRendererPtr != 0) { - nDestroyRenderer(mVectorState.mNativeRendererPtr); + if (mVectorState.mNativeRendererRefBase != null) { + mVectorState.mNativeRendererRefBase.release(); } - mVectorState.mNativeRendererPtr = nCreateRenderer(mVectorState.mRootGroup.mNativePtr); + mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr); } final VectorDrawableState state = mVectorState; state.setDensity(Drawable.resolveDensity(r, 0)); @@ -707,7 +709,7 @@ public class VectorDrawable extends Drawable { return mVectorState.mAutoMirrored; } - private static class VectorDrawableState extends ConstantState { + static class VectorDrawableState extends ConstantState { // Variables below need to be copied (deep copy if applicable) for mutation. int[] mThemeAttrs; int mChangingConfigurations; @@ -722,7 +724,7 @@ public class VectorDrawable extends Drawable { Insets mOpticalInsets = Insets.NONE; String mRootName = null; VGroup mRootGroup; - long mNativeRendererPtr; + VirtualRefBasePtr mNativeRendererRefBase = null; int mDensity = DisplayMetrics.DENSITY_DEFAULT; final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); @@ -743,7 +745,7 @@ public class VectorDrawable extends Drawable { mTintMode = copy.mTintMode; mAutoMirrored = copy.mAutoMirrored; mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); - mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr); + createNativeRenderer(mRootGroup.mNativePtr); mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; @@ -758,18 +760,15 @@ public class VectorDrawable extends Drawable { } } - @Override - public void finalize() throws Throwable { - if (mNativeRendererPtr != 0) { - nDestroyRenderer(mNativeRendererPtr); - mNativeRendererPtr = 0; - } - super.finalize(); + private void createNativeRenderer(long rootGroupPtr) { + mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr)); } - long getNativeRenderer() { - return mNativeRendererPtr; + if (mNativeRendererRefBase == null) { + return 0; + } + return mNativeRendererRefBase.get(); } public boolean canReuseCache() { @@ -808,7 +807,7 @@ public class VectorDrawable extends Drawable { public VectorDrawableState() { mRootGroup = new VGroup(); - mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr); + createNativeRenderer(mRootGroup.mNativePtr); } @Override @@ -872,16 +871,16 @@ public class VectorDrawable extends Drawable { * has changed. */ public boolean setAlpha(float alpha) { - return nSetRootAlpha(mNativeRendererPtr, alpha); + return nSetRootAlpha(mNativeRendererRefBase.get(), alpha); } @SuppressWarnings("unused") public float getAlpha() { - return nGetRootAlpha(mNativeRendererPtr); + return nGetRootAlpha(mNativeRendererRefBase.get()); } } - private static class VGroup implements VObject { + static class VGroup implements VObject { private static final int ROTATE_INDEX = 0; private static final int PIVOT_X_INDEX = 1; private static final int PIVOT_Y_INDEX = 2; @@ -891,6 +890,28 @@ public class VectorDrawable extends Drawable { private static final int TRANSLATE_Y_INDEX = 6; private static final int TRANSFORM_PROPERTY_COUNT = 7; + private static final HashMap<String, Integer> sPropertyMap = + new HashMap<String, Integer>() { + { + put("translateX", TRANSLATE_X_INDEX); + put("translateY", TRANSLATE_Y_INDEX); + put("scaleX", SCALE_X_INDEX); + put("scaleY", SCALE_Y_INDEX); + put("pivotX", PIVOT_X_INDEX); + put("pivotY", PIVOT_Y_INDEX); + put("rotation", ROTATE_INDEX); + } + }; + + static int getPropertyIndex(String propertyName) { + if (sPropertyMap.containsKey(propertyName)) { + return sPropertyMap.get(propertyName); + } else { + // property not found + return -1; + } + } + // Temp array to store transform values obtained from native. private float[] mTransform; ///////////////////////////////////////////////////// @@ -1149,7 +1170,7 @@ public class VectorDrawable extends Drawable { /** * Common Path information for clip path and normal path. */ - private static abstract class VPath implements VObject { + static abstract class VPath implements VObject { protected PathParser.PathData mPathData = null; String mPathName; @@ -1260,7 +1281,7 @@ public class VectorDrawable extends Drawable { /** * Normal path, which contains all the fill / paint information. */ - private static class VFullPath extends VPath { + static class VFullPath extends VPath { private static final int STROKE_WIDTH_INDEX = 0; private static final int STROKE_COLOR_INDEX = 1; private static final int STROKE_ALPHA_INDEX = 2; @@ -1274,6 +1295,20 @@ public class VectorDrawable extends Drawable { private static final int STROKE_MITER_LIMIT_INDEX = 10; private static final int TOTAL_PROPERTY_COUNT = 11; + private final static HashMap<String, Integer> sPropertyMap + = new HashMap<String, Integer> () { + { + put("strokeWidth", STROKE_WIDTH_INDEX); + put("strokeColor", STROKE_COLOR_INDEX); + put("strokeAlpha", STROKE_ALPHA_INDEX); + put("fillColor", FILL_COLOR_INDEX); + put("fillAlpha", FILL_ALPHA_INDEX); + put("trimPathStart", TRIM_PATH_START_INDEX); + put("trimPathEnd", TRIM_PATH_END_INDEX); + put("trimPathOffset", TRIM_PATH_OFFSET_INDEX); + } + }; + // Temp array to store property data obtained from native getter. private byte[] mPropertyData; ///////////////////////////////////////////////////// @@ -1297,6 +1332,14 @@ public class VectorDrawable extends Drawable { mFillColors = copy.mFillColors; } + int getPropertyIndex(String propertyName) { + if (!sPropertyMap.containsKey(propertyName)) { + return -1; + } else { + return sPropertyMap.get(propertyName); + } + } + @Override public boolean onStateChange(int[] stateSet) { boolean changed = false; @@ -1595,7 +1638,6 @@ public class VectorDrawable extends Drawable { } private static native long nCreateRenderer(long rootGroupPtr); - private static native void nDestroyRenderer(long rendererPtr); private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, float viewportHeight); private static native boolean nSetRootAlpha(long rendererPtr, float alpha); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index fc405541484f..a7779733c456 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -78,6 +78,8 @@ hwui_src_files := \ Program.cpp \ ProgramCache.cpp \ Properties.cpp \ + PropertyValuesHolder.cpp \ + PropertyValuesAnimatorSet.cpp \ RenderBufferCache.cpp \ RenderNode.cpp \ RenderProperties.cpp \ diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 5ca2a2fa37ab..7bd2b24bf56b 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -90,6 +90,9 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { doSetStartValue(getValue(mTarget)); } if (mStagingPlayState > mPlayState) { + if (mStagingPlayState == PlayState::Restarted) { + mStagingPlayState = PlayState::Running; + } mPlayState = mStagingPlayState; // Oh boy, we're starting! Man the battle stations! if (mPlayState == PlayState::Running) { @@ -131,6 +134,11 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { return true; } + // This should be set before setValue() so animators can query this time when setValue + // is called. + nsecs_t currentFrameTime = context.frameTimeMs(); + onPlayTimeChanged(currentFrameTime - mStartTime); + // If BaseRenderNodeAnimator is handling the delay (not typical), then // because the staging properties reflect the final value, we always need // to call setValue even if the animation isn't yet running or is still @@ -141,8 +149,9 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { } float fraction = 1.0f; + if (mPlayState == PlayState::Running && mDuration > 0) { - fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration; + fraction = (float)(currentFrameTime - mStartTime) / mDuration; } if (fraction >= 1.0f) { fraction = 1.0f; diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index aea95bfc1c0e..2c9c9c3fe0f9 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -59,7 +59,13 @@ public: mMayRunAsync = mayRunAsync; } bool mayRunAsync() { return mMayRunAsync; } - ANDROID_API void start() { mStagingPlayState = PlayState::Running; onStagingPlayStateChanged(); } + ANDROID_API void start() { + if (mStagingPlayState == PlayState::NotStarted) { + mStagingPlayState = PlayState::Running; + } else { + mStagingPlayState = PlayState::Restarted; + } + onStagingPlayStateChanged(); } ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); } void attach(RenderNode* target); @@ -77,10 +83,27 @@ public: void forceEndNow(AnimationContext& context); protected: + // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI + // thread and Render Thread animation state, respectively. + // From the UI thread, mStagingPlayState transition looks like + // NotStarted -> Running -> Finished + // ^ | + // | | + // Restarted <------ + // Note: For mStagingState, the Finished state (optional) is only set when the animation is + // terminated by user. + // + // On Render Thread, mPlayState transition: + // NotStart -> Running -> Finished + // ^ | + // | | + // ------------- + enum class PlayState { NotStarted, Running, Finished, + Restarted, }; BaseRenderNodeAnimator(float finalValue); @@ -93,6 +116,7 @@ protected: void callOnFinishedListener(AnimationContext& context); virtual void onStagingPlayStateChanged() {} + virtual void onPlayTimeChanged(nsecs_t playTime) {} RenderNode* mTarget; diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 9dfe454c7bc1..d7e2f09d4ba1 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -43,6 +43,13 @@ typedef uint32_t Flags; } // namespace SaveFlags +namespace uirenderer { +namespace VectorDrawable { +class Tree; +}; +}; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + class ANDROID_API Canvas { public: virtual ~Canvas() {}; @@ -185,6 +192,11 @@ public: */ virtual bool drawTextAbsolutePos() const = 0; + /** + * Draws a VectorDrawable onto the canvas. + */ + virtual void drawVectorDrawable(VectorDrawableRoot* tree); + protected: void drawTextDecorations(float x, float y, float length, const SkPaint& paint); }; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 384e64d7700f..5366127092d4 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -16,11 +16,12 @@ #include "DisplayListCanvas.h" -#include "ResourceCache.h" #include "DeferredDisplayList.h" #include "DeferredLayerUpdater.h" #include "DisplayListOp.h" +#include "ResourceCache.h" #include "RenderNode.h" +#include "VectorDrawable.h" #include "utils/PaintUtils.h" #include <SkCamera.h> @@ -412,6 +413,16 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint))); } +void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->ref(tree); + const SkBitmap& bitmap = tree->getBitmapUpdateIfDirty(); + SkPaint* paint = tree->getPaint(); + const SkRect bounds = tree->getBounds(); + addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap), + 0, 0, bitmap.width(), bitmap.height(), + bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), refPaint(paint))); +} + void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) { if (!glyphs || count <= 0) return; diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index f1cfa08b3ac2..d41fff425e81 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -209,6 +209,8 @@ public: float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) override; + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; + // Text virtual void drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, @@ -217,7 +219,6 @@ public: float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } - private: CanvasState mState; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index a45721249542..4740e1f13bda 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -19,6 +19,7 @@ #include "Canvas.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" +#include "VectorDrawable.h" #include "renderstate/OffscreenBufferPool.h" #include "utils/FatVector.h" #include "utils/PaintUtils.h" @@ -534,6 +535,18 @@ void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); } +void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { + const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); + SkPaint* paint = op.vectorDrawable->getPaint(); + const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds, + op.localMatrix, + op.localClip, + paint, + &bitmap, + Rect(bitmap.width(), bitmap.height())); + deferBitmapRectOp(*resolvedOp); +} + void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { // allocate a temporary oval op (with mAllocator, so it persists until render), so the // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp new file mode 100644 index 000000000000..eca1afcc54dc --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesAnimatorSet.h" +#include "RenderNode.h" + +namespace android { +namespace uirenderer { + +void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount) { + + PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder, + interpolator, startDelay, duration, repeatCount); + mAnimators.emplace_back(animator); + setListener(new PropertyAnimatorSetListener(this)); +} + +PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() + : BaseRenderNodeAnimator(1.0f) { + setStartValue(0); + mLastFraction = 0.0f; + setInterpolator(new LinearInterpolator()); +} + +void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) { + if (mOneShotListener.get()) { + mOneShotListener->onAnimationFinished(animator); + mOneShotListener = nullptr; + } +} + +float PropertyValuesAnimatorSet::getValue(RenderNode* target) const { + return mLastFraction; +} + +void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) { + mLastFraction = value; +} + +void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) { + for (size_t i = 0; i < mAnimators.size(); i++) { + mAnimators[i]->setCurrentPlayTime(playTime); + } +} + +void PropertyValuesAnimatorSet::reset() { + // TODO: implement reset through adding a play state because we need to support reset() even + // during an animation run. +} + +void PropertyValuesAnimatorSet::start(AnimationListener* listener) { + init(); + mOneShotListener = listener; + BaseRenderNodeAnimator::start(); +} + +void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) { +// TODO: implement reverse +} + +void PropertyValuesAnimatorSet::init() { + if (mInitialized) { + return; + } + nsecs_t maxDuration = 0; + for (size_t i = 0; i < mAnimators.size(); i++) { + if (maxDuration < mAnimators[i]->getTotalDuration()) { + maxDuration = mAnimators[i]->getTotalDuration(); + } + } + mDuration = maxDuration; + mInitialized = true; +} + +uint32_t PropertyValuesAnimatorSet::dirtyMask() { + return RenderNode::DISPLAY_LIST; +} + +PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, + nsecs_t startDelay, nsecs_t duration, int repeatCount) + : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay), + mDuration(duration) { + if (repeatCount < 0) { + mRepeatCount = UINT32_MAX; + } else { + mRepeatCount = repeatCount; + } + mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay; +} + +void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) { + if (playTime >= mStartDelay && playTime < mTotalDuration) { + nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration; + mLatestFraction = currentIterationPlayTime / (float) mDuration; + } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) { + mLatestFraction = 1.0f; + } else { + return; + } + + setFraction(mLatestFraction); +} + +void PropertyAnimator::setFraction(float fraction) { + float interpolatedFraction = mInterpolator->interpolate(mLatestFraction); + mPropertyValuesHolder->setFraction(interpolatedFraction); +} + +void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) { + mSet->onFinished(animator); +} + +} +} diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h new file mode 100644 index 000000000000..4c7ce528bb20 --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "Animator.h" +#include "PropertyValuesHolder.h" +#include "Interpolator.h" + +namespace android { +namespace uirenderer { + +class PropertyAnimator { +public: + PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount); + void setCurrentPlayTime(nsecs_t playTime); + nsecs_t getTotalDuration() { + return mTotalDuration; + } + void setFraction(float fraction); + +private: + std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder; + std::unique_ptr<Interpolator> mInterpolator; + nsecs_t mStartDelay; + nsecs_t mDuration; + uint32_t mRepeatCount; + nsecs_t mTotalDuration; + float mLatestFraction = 0.0f; +}; + +class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator { +public: + friend class PropertyAnimatorSetListener; + PropertyValuesAnimatorSet(); + + void start(AnimationListener* listener); + void reverse(AnimationListener* listener); + void reset(); + + void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolators, int64_t startDelays, + nsecs_t durations, int repeatCount); + virtual uint32_t dirtyMask(); + +protected: + virtual float getValue(RenderNode* target) const override; + virtual void setValue(RenderNode* target, float value) override; + virtual void onPlayTimeChanged(nsecs_t playTime) override; + +private: + void init(); + void onFinished(BaseRenderNodeAnimator* animator); + // Listener set from outside + sp<AnimationListener> mOneShotListener; + std::vector< std::unique_ptr<PropertyAnimator> > mAnimators; + float mLastFraction = 0.0f; + bool mInitialized = false; +}; + +class PropertyAnimatorSetListener : public AnimationListener { +public: + PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {} + virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override; + +private: + PropertyValuesAnimatorSet* mSet; +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp new file mode 100644 index 000000000000..8f837f6048d6 --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesHolder.h" + +#include "utils/VectorDrawableUtils.h" + +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +using namespace VectorDrawable; + +float PropertyValuesHolder::getValueFromData(float fraction) { + if (mDataSource.size() == 0) { + LOG_ALWAYS_FATAL("No data source is defined"); + return 0; + } + if (fraction <= 0.0f) { + return mDataSource.front(); + } + if (fraction >= 1.0f) { + return mDataSource.back(); + } + + fraction *= mDataSource.size() - 1; + int lowIndex = floor(fraction); + fraction -= lowIndex; + + float value = mDataSource[lowIndex] * (1.0f - fraction) + + mDataSource[lowIndex + 1] * fraction; + return value; +} + +void GroupPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mGroup->setPropertyValue(mPropertyId, animatedValue); +} + +inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { + return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +} + +// TODO: Add a test for this +SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor, + float fraction) { + U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); + U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); + U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); + U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); + return SkColorSetARGB(alpha, red, green, blue); +} + +void FullPathColorPropertyValuesHolder::setFraction(float fraction) { + SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction); + mFullPath->setColorPropertyValue(mPropertyId, animatedValue); +} + +void FullPathPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mFullPath->setPropertyValue(mPropertyId, animatedValue); +} + +void PathDataPropertyValuesHolder::setFraction(float fraction) { + VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction); + mPath->setPathData(mPathData); +} + +void RootAlphaPropertyValuesHolder::setFraction(float fraction) { + float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + mTree->setRootAlpha(animatedValue); +} + +} // namepace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h new file mode 100644 index 000000000000..b905faef104c --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "VectorDrawable.h" + +#include <SkColor.h> + +namespace android { +namespace uirenderer { + +/** + * PropertyValues holder contains data needed to change a property of a Vector Drawable object. + * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based + * on its start and end value, and set the new value on the VectorDrawble's corresponding property. + */ +class ANDROID_API PropertyValuesHolder { +public: + virtual void setFraction(float fraction) = 0; + void setPropertyDataSource(float* dataSource, int length) { + mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length); + } + float getValueFromData(float fraction); + virtual ~PropertyValuesHolder() {} +protected: + std::vector<float> mDataSource; +}; + +class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder { +public: + GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue, + float endValue) + : mGroup(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue){ + } + void setFraction(float fraction) override; +private: + VectorDrawable::Group* mGroup; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue, + int32_t endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; + static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction); +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + int32_t mStartValue; + int32_t mEndValue; +}; + +class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue, + float endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder { +public: + PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue, + PathData* endValue) + : mPath(ptr) + , mStartValue(*startValue) + , mEndValue(*endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::Path* mPath; + PathData mPathData; + PathData mStartValue; + PathData mEndValue; +}; + +class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder { +public: + RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue) + : mTree(tree) + , mStartValue(startValue) + , mEndValue(endValue) {} + void setFraction(float fraction) override; +private: + VectorDrawable::Tree* mTree; + float mStartValue; + float mEndValue; +}; +} +} diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index 30d5c293c4bb..d27064b4e43e 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -17,6 +17,7 @@ #ifndef ANDROID_HWUI_RECORDED_OP_H #define ANDROID_HWUI_RECORDED_OP_H +#include "RecordedOp.h" #include "font/FontUtil.h" #include "Matrix.h" #include "Rect.h" @@ -38,6 +39,10 @@ class OffscreenBuffer; class RenderNode; struct Vertex; +namespace VectorDrawable { +class Tree; +} + /** * Authoritative op list, used for generating the op ID enum, ID based LUTS, and * the functions to which they dispatch. Parameter macros are executed for each op, @@ -74,6 +79,7 @@ struct Vertex; PRE_RENDER_OP_FN(EndLayerOp) \ PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \ PRE_RENDER_OP_FN(EndUnclippedLayerOp) \ + PRE_RENDER_OP_FN(VectorDrawableOp) \ \ RENDER_ONLY_OP_FN(ShadowOp) \ RENDER_ONLY_OP_FN(LayerOp) \ @@ -324,6 +330,13 @@ struct RoundRectPropsOp : RecordedOp { const float* ry; }; +struct VectorDrawableOp : RecordedOp { + VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS) + : SUPER_PAINTLESS(VectorDrawableOp) + , vectorDrawable(tree) {} + VectorDrawable::Tree* vectorDrawable; +}; + /** * Real-time, dynamic-lit shadow. * diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 328e291a60af..abbd9c38d0ad 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -19,6 +19,7 @@ #include "DeferredLayerUpdater.h" #include "RecordedOp.h" #include "RenderNode.h" +#include "VectorDrawable.h" namespace android { namespace uirenderer { @@ -395,7 +396,6 @@ void RecordingCanvas::drawCircle( &x->value, &y->value, &radius->value)); } - void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { addOp(new (alloc()) OvalOp( Rect(left, top, right, bottom), @@ -422,6 +422,15 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { refPaint(&paint), refPath(&path))); } +void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->ref(tree); + addOp(new (alloc()) VectorDrawableOp( + tree, + Rect(tree->getBounds()), + *(mState.currentSnapshot()->transform), + getRecordedClip())); +} + // Bitmap-based void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { save(SaveFlags::Matrix); diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 786f96e852ec..7c8ad8814d32 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -178,6 +178,8 @@ public: const uint16_t* indices, int indexCount, const SkPaint& paint) override { /* RecordingCanvas does not support drawVertices(); ignore */ } + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; + // Bitmap-based virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override; virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 20e7c711a9ce..550995b2b9c6 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -27,6 +27,8 @@ #include <SkTLazy.h> #include <SkTemplates.h> +#include "VectorDrawable.h" + #include <memory> namespace android { @@ -136,6 +138,7 @@ public: float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return true; } + virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; private: struct SaveRec { @@ -713,6 +716,14 @@ void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chu NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr); } +void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { + const SkBitmap& bitmap = vectorDrawable->getBitmapUpdateIfDirty(); + SkRect bounds = vectorDrawable->getBounds(); + drawBitmap(bitmap, 0, 0, bitmap.width(), bitmap.height(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, + vectorDrawable->getPaint()); +} + // ---------------------------------------------------------------------------- // Canvas draw operations: Text // ---------------------------------------------------------------------------- diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 1cf15ac01154..c72f87d7e481 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -138,18 +138,7 @@ void Path::setPath(const char* pathStr, size_t strLength) { } FullPath::FullPath(const FullPath& path) : Path(path) { - mStrokeWidth = path.mStrokeWidth; - mStrokeColor = path.mStrokeColor; - mStrokeAlpha = path.mStrokeAlpha; - mFillColor = path.mFillColor; - mFillAlpha = path.mFillAlpha; - mTrimPathStart = path.mTrimPathStart; - mTrimPathEnd = path.mTrimPathEnd; - mTrimPathOffset = path.mTrimPathOffset; - mStrokeMiterLimit = path.mStrokeMiterLimit; - mStrokeLineCap = path.mStrokeLineCap; - mStrokeLineJoin = path.mStrokeLineJoin; - + mProperties = path.mProperties; SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient); SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient); } @@ -159,7 +148,7 @@ const SkPath& FullPath::getUpdatedPath() { return mTrimmedSkPath; } Path::getUpdatedPath(); - if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) { + if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) { applyTrim(); return mTrimmedSkPath; } else { @@ -170,14 +159,14 @@ const SkPath& FullPath::getUpdatedPath() { void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) { - mStrokeWidth = strokeWidth; - mStrokeColor = strokeColor; - mStrokeAlpha = strokeAlpha; - mFillColor = fillColor; - mFillAlpha = fillAlpha; - mStrokeMiterLimit = strokeMiterLimit; - mStrokeLineCap = SkPaint::Cap(strokeLineCap); - mStrokeLineJoin = SkPaint::Join(strokeLineJoin); + mProperties.strokeWidth = strokeWidth; + mProperties.strokeColor = strokeColor; + mProperties.strokeAlpha = strokeAlpha; + mProperties.fillColor = fillColor; + mProperties.fillAlpha = fillAlpha; + mProperties.strokeMiterLimit = strokeMiterLimit; + mProperties.strokeLineCap = strokeLineCap; + mProperties.strokeLineJoin = strokeLineJoin; // If any trim property changes, mark trim dirty and update the trim path setTrimPathStart(trimPathStart); @@ -195,12 +184,12 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str // Draw path's fill, if fill color or gradient is valid bool needsFill = false; if (mFillGradient != nullptr) { - mPaint.setColor(applyAlpha(SK_ColorBLACK, mFillAlpha)); + mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha)); SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix); mPaint.setShader(newShader); needsFill = true; - } else if (mFillColor != SK_ColorTRANSPARENT) { - mPaint.setColor(applyAlpha(mFillColor, mFillAlpha)); + } else if (mProperties.fillColor != SK_ColorTRANSPARENT) { + mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha)); needsFill = true; } @@ -213,21 +202,21 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str // Draw path's stroke, if stroke color or gradient is valid bool needsStroke = false; if (mStrokeGradient != nullptr) { - mPaint.setColor(applyAlpha(SK_ColorBLACK, mStrokeAlpha)); + mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha)); SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix); mPaint.setShader(newShader); needsStroke = true; - } else if (mStrokeColor != SK_ColorTRANSPARENT) { - mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha)); + } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) { + mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha)); needsStroke = true; } if (needsStroke) { mPaint.setStyle(SkPaint::Style::kStroke_Style); mPaint.setAntiAlias(true); - mPaint.setStrokeJoin(mStrokeLineJoin); - mPaint.setStrokeCap(mStrokeLineCap); - mPaint.setStrokeMiter(mStrokeMiterLimit); - mPaint.setStrokeWidth(mStrokeWidth * strokeScale); + mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin)); + mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap)); + mPaint.setStrokeMiter(mProperties.strokeMiterLimit); + mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale); outCanvas->drawPath(renderPath, mPaint); } } @@ -236,14 +225,14 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str * Applies trimming to the specified path. */ void FullPath::applyTrim() { - if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) { + if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) { // No trimming necessary. return; } SkPathMeasure measure(mSkPath, false); float len = SkScalarToFloat(measure.getLength()); - float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f); - float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f); + float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f); + float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f); mTrimmedSkPath.reset(); if (start > end) { @@ -255,76 +244,69 @@ void FullPath::applyTrim() { mTrimDirty = false; } -inline int putData(int8_t* outBytes, int startIndex, float value) { - int size = sizeof(float); - memcpy(&outBytes[startIndex], &value, size); - return size; -} - -inline int putData(int8_t* outBytes, int startIndex, int value) { - int size = sizeof(int); - memcpy(&outBytes[startIndex], &value, size); - return size; -} - -struct FullPathProperties { - // TODO: Consider storing full path properties in this struct instead of the fields. - float strokeWidth; - SkColor strokeColor; - float strokeAlpha; - SkColor fillColor; - float fillAlpha; - float trimPathStart; - float trimPathEnd; - float trimPathOffset; - int32_t strokeLineCap; - int32_t strokeLineJoin; - float strokeMiterLimit; -}; - -REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties); +REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties); static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t"); static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t"); bool FullPath::getProperties(int8_t* outProperties, int length) { - int propertyDataSize = sizeof(FullPathProperties); + int propertyDataSize = sizeof(Properties); if (length != propertyDataSize) { LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", propertyDataSize, length); return false; } - // TODO: consider replacing the property fields with a FullPathProperties struct. - FullPathProperties properties; - properties.strokeWidth = mStrokeWidth; - properties.strokeColor = mStrokeColor; - properties.strokeAlpha = mStrokeAlpha; - properties.fillColor = mFillColor; - properties.fillAlpha = mFillAlpha; - properties.trimPathStart = mTrimPathStart; - properties.trimPathEnd = mTrimPathEnd; - properties.trimPathOffset = mTrimPathOffset; - properties.strokeLineCap = mStrokeLineCap; - properties.strokeLineJoin = mStrokeLineJoin; - properties.strokeMiterLimit = mStrokeMiterLimit; - - memcpy(outProperties, &properties, length); + Properties* out = reinterpret_cast<Properties*>(outProperties); + *out = mProperties; return true; } +void FullPath::setColorPropertyValue(int propertyId, int32_t value) { + Property currentProperty = static_cast<Property>(propertyId); + if (currentProperty == Property::StrokeColor) { + mProperties.strokeColor = value; + } else if (currentProperty == Property::FillColor) { + mProperties.fillColor = value; + } else { + LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d", + propertyId); + } +} + +void FullPath::setPropertyValue(int propertyId, float value) { + Property property = static_cast<Property>(propertyId); + switch (property) { + case Property::StrokeWidth: + setStrokeWidth(value); + break; + case Property::StrokeAlpha: + setStrokeAlpha(value); + break; + case Property::FillAlpha: + setFillAlpha(value); + break; + case Property::TrimPathStart: + setTrimPathStart(value); + break; + case Property::TrimPathEnd: + setTrimPathEnd(value); + break; + case Property::TrimPathOffset: + setTrimPathOffset(value); + break; + default: + LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId); + break; + } +} + void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale, const SkMatrix& matrix){ outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); } Group::Group(const Group& group) : Node(group) { - mRotate = group.mRotate; - mPivotX = group.mPivotX; - mPivotY = group.mPivotY; - mScaleX = group.mScaleX; - mScaleY = group.mScaleY; - mTranslateX = group.mTranslateX; - mTranslateY = group.mTranslateY; + mProperties = group.mProperties; } void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, @@ -371,10 +353,11 @@ void Group::getLocalMatrix(SkMatrix* outMatrix) { outMatrix->reset(); // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of // translating to pivot for rotating and scaling, then translating back. - outMatrix->postTranslate(-mPivotX, -mPivotY); - outMatrix->postScale(mScaleX, mScaleY); - outMatrix->postRotate(mRotate, 0, 0); - outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY); + outMatrix->postScale(mProperties.scaleX, mProperties.scaleY); + outMatrix->postRotate(mProperties.rotate, 0, 0); + outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX, + mProperties.translateY + mProperties.pivotY); } void Group::addChild(Node* child) { @@ -388,38 +371,68 @@ bool Group::getProperties(float* outProperties, int length) { propertyCount, length); return false; } - for (int i = 0; i < propertyCount; i++) { - Property currentProperty = static_cast<Property>(i); - switch (currentProperty) { - case Property::Rotate_Property: - outProperties[i] = mRotate; - break; - case Property::PivotX_Property: - outProperties[i] = mPivotX; - break; - case Property::PivotY_Property: - outProperties[i] = mPivotY; - break; - case Property::ScaleX_Property: - outProperties[i] = mScaleX; - break; - case Property::ScaleY_Property: - outProperties[i] = mScaleY; - break; - case Property::TranslateX_Property: - outProperties[i] = mTranslateX; - break; - case Property::TranslateY_Property: - outProperties[i] = mTranslateY; - break; - default: - LOG_ALWAYS_FATAL("Invalid input index: %d", i); - return false; - } - } + Properties* out = reinterpret_cast<Properties*>(outProperties); + *out = mProperties; return true; } +// TODO: Consider animating the properties as float pointers +float Group::getPropertyValue(int propertyId) const { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::Rotate: + return mProperties.rotate; + case Property::PivotX: + return mProperties.pivotX; + case Property::PivotY: + return mProperties.pivotY; + case Property::ScaleX: + return mProperties.scaleX; + case Property::ScaleY: + return mProperties.scaleY; + case Property::TranslateX: + return mProperties.translateX; + case Property::TranslateY: + return mProperties.translateY; + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + return 0; + } +} + +void Group::setPropertyValue(int propertyId, float value) { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::Rotate: + mProperties.rotate = value; + break; + case Property::PivotX: + mProperties.pivotX = value; + break; + case Property::PivotY: + mProperties.pivotY = value; + break; + case Property::ScaleX: + mProperties.scaleX = value; + break; + case Property::ScaleY: + mProperties.scaleY = value; + break; + case Property::TranslateX: + mProperties.translateX = value; + break; + case Property::TranslateY: + mProperties.translateY = value; + break; + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + } +} + +bool Group::isValidProperty(int propertyId) { + return propertyId >= 0 && propertyId < static_cast<int>(Property::Count); +} + void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, const SkRect& bounds, bool needsMirroring, bool canReuseCache) { // The imageView can scale the canvas in different ways, in order to @@ -445,7 +458,9 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, return; } - int saveCount = outCanvas->save(SaveFlags::MatrixClip); + mPaint.setColorFilter(colorFilter); + + int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag); outCanvas->translate(mBounds.fLeft, mBounds.fTop); // Handle RTL mirroring. @@ -458,43 +473,33 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, // And we use this bound for the destination rect for the drawBitmap, so // we offset to (0, 0); mBounds.offsetTo(0, 0); - createCachedBitmapIfNeeded(scaledWidth, scaledHeight); - if (!mAllowCaching) { - updateCachedBitmap(scaledWidth, scaledHeight); - } else { - if (!canReuseCache || mCacheDirty) { - updateCachedBitmap(scaledWidth, scaledHeight); - } - } - drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds); + + outCanvas->drawVectorDrawable(this); outCanvas->restoreToCount(saveCount); } -void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, - const SkRect& originalBounds) { +SkPaint* Tree::getPaint() { SkPaint* paint; - if (mRootAlpha == 1.0f && filter == NULL) { + if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) { paint = NULL; } else { mPaint.setFilterQuality(kLow_SkFilterQuality); mPaint.setAlpha(mRootAlpha * 255); - mPaint.setColorFilter(filter); paint = &mPaint; } - outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(), - originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight, - originalBounds.fBottom, paint); + return paint; } -void Tree::updateCachedBitmap(int width, int height) { +const SkBitmap& Tree::getBitmapUpdateIfDirty() { mCachedBitmap.eraseColor(SK_ColorTRANSPARENT); SkCanvas outCanvas(mCachedBitmap); - float scaleX = width / mViewportWidth; - float scaleY = height / mViewportHeight; + float scaleX = (float) mCachedBitmap.width() / mViewportWidth; + float scaleY = (float) mCachedBitmap.height() / mViewportHeight; mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY); mCacheDirty = false; + return mCachedBitmap; } void Tree::createCachedBitmapIfNeeded(int width, int height) { diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 09bdce596a21..f8f1ea62a624 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -18,6 +18,7 @@ #define ANDROID_HWUI_VPATH_H #include "Canvas.h" + #include <SkBitmap.h> #include <SkColor.h> #include <SkCanvas.h> @@ -104,6 +105,21 @@ protected: class ANDROID_API FullPath: public Path { public: + +struct Properties { + float strokeWidth = 0; + SkColor strokeColor = SK_ColorTRANSPARENT; + float strokeAlpha = 1; + SkColor fillColor = SK_ColorTRANSPARENT; + float fillAlpha = 1; + float trimPathStart = 0; + float trimPathEnd = 1; + float trimPathOffset = 0; + int32_t strokeLineCap = SkPaint::Cap::kButt_Cap; + int32_t strokeLineJoin = SkPaint::Join::kMiter_Join; + float strokeMiterLimit = 4; +}; + FullPath(const FullPath& path); // for cloning FullPath(const char* path, size_t strLength) : Path(path, strLength) {} FullPath() : Path() {} @@ -118,55 +134,58 @@ public: float strokeAlpha, SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin); + // TODO: Cleanup: Remove the setter and getters below, and their counterparts in java and JNI float getStrokeWidth() { - return mStrokeWidth; + return mProperties.strokeWidth; } void setStrokeWidth(float strokeWidth) { - mStrokeWidth = strokeWidth; + mProperties.strokeWidth = strokeWidth; } SkColor getStrokeColor() { - return mStrokeColor; + return mProperties.strokeColor; } void setStrokeColor(SkColor strokeColor) { - mStrokeColor = strokeColor; + mProperties.strokeColor = strokeColor; } float getStrokeAlpha() { - return mStrokeAlpha; + return mProperties.strokeAlpha; } void setStrokeAlpha(float strokeAlpha) { - mStrokeAlpha = strokeAlpha; + mProperties.strokeAlpha = strokeAlpha; } SkColor getFillColor() { - return mFillColor; + return mProperties.fillColor; } void setFillColor(SkColor fillColor) { - mFillColor = fillColor; + mProperties.fillColor = fillColor; } float getFillAlpha() { - return mFillAlpha; + return mProperties.fillAlpha; } void setFillAlpha(float fillAlpha) { - mFillAlpha = fillAlpha; + mProperties.fillAlpha = fillAlpha; } float getTrimPathStart() { - return mTrimPathStart; + return mProperties.trimPathStart; } void setTrimPathStart(float trimPathStart) { - VD_SET_PROP_WITH_FLAG(mTrimPathStart, trimPathStart, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathStart, trimPathStart, mTrimDirty); } float getTrimPathEnd() { - return mTrimPathEnd; + return mProperties.trimPathEnd; } void setTrimPathEnd(float trimPathEnd) { - VD_SET_PROP_WITH_FLAG(mTrimPathEnd, trimPathEnd, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathEnd, trimPathEnd, mTrimDirty); } float getTrimPathOffset() { - return mTrimPathOffset; + return mProperties.trimPathOffset; } void setTrimPathOffset(float trimPathOffset) { - VD_SET_PROP_WITH_FLAG(mTrimPathOffset, trimPathOffset, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathOffset, trimPathOffset, mTrimDirty); } bool getProperties(int8_t* outProperties, int length); + void setColorPropertyValue(int propertyId, int32_t value); + void setPropertyValue(int propertyId, float value); void setFillGradient(SkShader* fillGradient) { SkRefCnt_SafeAssign(mFillGradient, fillGradient); @@ -182,24 +201,28 @@ protected: float strokeScale, const SkMatrix& matrix) override; private: + enum class Property { + StrokeWidth = 0, + StrokeColor, + StrokeAlpha, + FillColor, + FillAlpha, + TrimPathStart, + TrimPathEnd, + TrimPathOffset, + StrokeLineCap, + StrokeLineJoin, + StrokeMiterLimit, + Count, + }; // Applies trimming to the specified path. void applyTrim(); - float mStrokeWidth = 0; - SkColor mStrokeColor = SK_ColorTRANSPARENT; - float mStrokeAlpha = 1; - SkColor mFillColor = SK_ColorTRANSPARENT; - SkShader* mStrokeGradient = nullptr; - SkShader* mFillGradient = nullptr; - float mFillAlpha = 1; - float mTrimPathStart = 0; - float mTrimPathEnd = 1; - float mTrimPathOffset = 0; + Properties mProperties; bool mTrimDirty = true; - SkPaint::Cap mStrokeLineCap = SkPaint::Cap::kButt_Cap; - SkPaint::Join mStrokeLineJoin = SkPaint::Join::kMiter_Join; - float mStrokeMiterLimit = 4; SkPath mTrimmedSkPath; SkPaint mPaint; + SkShader* mStrokeGradient = nullptr; + SkShader* mFillGradient = nullptr; }; class ANDROID_API ClipPath: public Path { @@ -216,49 +239,58 @@ protected: class ANDROID_API Group: public Node { public: + struct Properties { + float rotate = 0; + float pivotX = 0; + float pivotY = 0; + float scaleX = 1; + float scaleY = 1; + float translateX = 0; + float translateY = 0; + }; Group(const Group& group); Group() {} float getRotation() { - return mRotate; + return mProperties.rotate; } void setRotation(float rotation) { - mRotate = rotation; + mProperties.rotate = rotation; } float getPivotX() { - return mPivotX; + return mProperties.pivotX; } void setPivotX(float pivotX) { - mPivotX = pivotX; + mProperties.pivotX = pivotX; } float getPivotY() { - return mPivotY; + return mProperties.pivotY; } void setPivotY(float pivotY) { - mPivotY = pivotY; + mProperties.pivotY = pivotY; } float getScaleX() { - return mScaleX; + return mProperties.scaleX; } void setScaleX(float scaleX) { - mScaleX = scaleX; + mProperties.scaleX = scaleX; } float getScaleY() { - return mScaleY; + return mProperties.scaleY; } void setScaleY(float scaleY) { - mScaleY = scaleY; + mProperties.scaleY = scaleY; } float getTranslateX() { - return mTranslateX; + return mProperties.translateX; } void setTranslateX(float translateX) { - mTranslateX = translateX; + mProperties.translateX = translateX; } float getTranslateY() { - return mTranslateY; + return mProperties.translateY; } void setTranslateY(float translateY) { - mTranslateY = translateY; + mProperties.translateY = translateY; } virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, float scaleY) override; @@ -268,38 +300,33 @@ public: void addChild(Node* child); void dump() override; bool getProperties(float* outProperties, int length); + float getPropertyValue(int propertyId) const; + void setPropertyValue(int propertyId, float value); + static bool isValidProperty(int propertyId); private: enum class Property { - Rotate_Property = 0, - PivotX_Property, - PivotY_Property, - ScaleX_Property, - ScaleY_Property, - TranslateX_Property, - TranslateY_Property, + Rotate = 0, + PivotX, + PivotY, + ScaleX, + ScaleY, + TranslateX, + TranslateY, // Count of the properties, must be at the end. Count, }; - float mRotate = 0; - float mPivotX = 0; - float mPivotY = 0; - float mScaleX = 1; - float mScaleY = 1; - float mTranslateX = 0; - float mTranslateY = 0; std::vector<Node*> mChildren; + Properties mProperties; }; -class ANDROID_API Tree { +class ANDROID_API Tree : public VirtualLightRefBase { public: Tree(Group* rootNode) : mRootNode(rootNode) {} void draw(Canvas* outCanvas, SkColorFilter* colorFilter, const SkRect& bounds, bool needsMirroring, bool canReuseCache); - void drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, - const SkRect& originalBounds); - void updateCachedBitmap(int width, int height); + const SkBitmap& getBitmapUpdateIfDirty(); void createCachedBitmapIfNeeded(int width, int height); bool canReuseBitmap(int width, int height); void setAllowCaching(bool allowCaching) { @@ -316,6 +343,10 @@ public: mViewportWidth = viewportWidth; mViewportHeight = viewportHeight; } + SkPaint* getPaint(); + const SkRect& getBounds() const { + return mBounds; + } private: // Cap the bitmap size, such that it won't hurt the performance too much |