Optimize fill
diff --git a/lottie/src/main/java/com/airbnb/lottie/L.java b/lottie/src/main/java/com/airbnb/lottie/L.java
index a27d7da..68f7ca1 100644
--- a/lottie/src/main/java/com/airbnb/lottie/L.java
+++ b/lottie/src/main/java/com/airbnb/lottie/L.java
@@ -5,7 +5,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.core.os.TraceCompat;
import com.airbnb.lottie.network.DefaultLottieNetworkFetcher;
import com.airbnb.lottie.network.LottieNetworkCacheProvider;
@@ -15,6 +14,8 @@
import com.airbnb.lottie.utils.LottieTrace;
import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
@RestrictTo(RestrictTo.Scope.LIBRARY)
public class L {
@@ -33,6 +34,8 @@
private static volatile NetworkCache networkCache;
private static ThreadLocal<LottieTrace> lottieTrace;
+ public static AtomicLong drawTimeNs = new AtomicLong(0L);
+
private L() {
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index e493288..2d39708 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -680,7 +680,10 @@
renderAndDrawAsBitmap(canvas, compositionLayer);
canvas.restore();
} else {
+ long start = System.nanoTime();
compositionLayer.draw(canvas, matrix, alpha);
+ long end = System.nanoTime();
+ L.drawTimeNs.getAndAdd(end - start);
}
isDirty = false;
} catch (InterruptedException e) {
@@ -1560,6 +1563,7 @@
return;
}
+ long start = System.nanoTime();
renderingMatrix.reset();
Rect bounds = getBounds();
if (!bounds.isEmpty()) {
@@ -1571,6 +1575,8 @@
renderingMatrix.preTranslate(bounds.left, bounds.top);
}
compositionLayer.draw(canvas, renderingMatrix, alpha);
+ long end = System.nanoTime();
+ L.drawTimeNs.getAndAdd(end - start);
}
/**
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/ContentGroup.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/ContentGroup.java
index cc3d166..2fc907a 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/ContentGroup.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/ContentGroup.java
@@ -30,6 +30,7 @@
private final Paint offScreenPaint = new LPaint();
private final RectF offScreenRectF = new RectF();
+ private int generation;
private static List<Content> contentsFromModels(LottieDrawable drawable, LottieComposition composition, BaseLayer layer,
List<ContentModel> contentModels) {
@@ -160,6 +161,17 @@
return path;
}
+ @Override public int getGeneration() {
+ int result = 0;
+ for (int i = contents.size() - 1; i >= 0; i--) {
+ Content content = contents.get(i);
+ if (content instanceof PathContent) {
+ result = 31 * result + ((PathContent) content).getGeneration();
+ }
+ }
+ return result;
+ }
+
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
if (hidden) {
return;
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/EllipseContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/EllipseContent.java
index 031d8d5..7e49f01 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/EllipseContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/EllipseContent.java
@@ -31,6 +31,7 @@
private final CompoundTrimPathContent trimPaths = new CompoundTrimPathContent();
private boolean isPathValid;
+ private int generation = 0;
public EllipseContent(LottieDrawable lottieDrawable, BaseLayer layer, CircleShape circleShape) {
name = circleShape.getName();
@@ -52,6 +53,7 @@
private void invalidate() {
isPathValid = false;
+ generation++;
lottieDrawable.invalidateSelf();
}
@@ -116,6 +118,10 @@
return path;
}
+ @Override public int getGeneration() {
+ return generation;
+ }
+
@Override public void resolveKeyPath(
KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
MiscUtils.resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this);
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
index 46732a8..cec25b7 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
@@ -4,6 +4,7 @@
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -32,6 +33,8 @@
public class FillContent
implements DrawingContent, BaseKeyframeAnimation.AnimationListener, KeyPathElementContent {
private final Path path = new Path();
+ private final Matrix pathWithParentMatrixMatrix = new Matrix();
+ private int pathGeneration = -23094820;
private final Paint paint = new LPaint(Paint.ANTI_ALIAS_FLAG);
private final BaseLayer layer;
private final String name;
@@ -44,6 +47,12 @@
@Nullable private BaseKeyframeAnimation<Float, Float> blurAnimation;
float blurMaskFilterRadius;
+ private static final int DIRTY_FLAG_COLOR = 1;
+ private static final int DIRTY_FLAG_OPACITY = 1 << 1;
+ private int colorDirtyFlags = DIRTY_FLAG_COLOR | DIRTY_FLAG_OPACITY;
+ private int colorFromAnimations = 0;
+ private int lastAlpha = Integer.MAX_VALUE;
+
@Nullable private DropShadowKeyframeAnimation dropShadowAnimation;
public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFill fill) {
@@ -69,10 +78,16 @@
path.setFillType(fill.getFillType());
colorAnimation = fill.getColor().createAnimation();
- colorAnimation.addUpdateListener(this);
+ colorAnimation.addUpdateListener(() -> {
+ colorDirtyFlags |= DIRTY_FLAG_COLOR;
+ onValueChanged();
+ });
layer.addAnimation(colorAnimation);
opacityAnimation = fill.getOpacity().createAnimation();
- opacityAnimation.addUpdateListener(this);
+ opacityAnimation.addUpdateListener(() -> {
+ colorDirtyFlags |= DIRTY_FLAG_OPACITY;
+ onValueChanged();
+ });
layer.addAnimation(opacityAnimation);
}
@@ -98,9 +113,23 @@
return;
}
L.beginSection("FillContent#draw");
- int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
- int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
- paint.setColor((clamp(alpha, 0, 255) << 24) | (color & 0xFFFFFF));
+
+ boolean hasDirtyFlags = colorDirtyFlags != 0;
+ if ((colorDirtyFlags & DIRTY_FLAG_COLOR) != 0) {
+ int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
+ colorFromAnimations = (colorFromAnimations & 0xFF000000) | (color & 0xFFFFFF);
+ colorDirtyFlags &= ~DIRTY_FLAG_COLOR;
+ }
+ if ((colorDirtyFlags & DIRTY_FLAG_OPACITY) != 0) {
+ int alpha = (int) ((opacityAnimation.getValue() / 100f) * 255);
+ colorFromAnimations = (clamp(alpha, 0, 255) << 24) | (colorFromAnimations & 0xFFFFFF);
+ colorDirtyFlags &= ~DIRTY_FLAG_OPACITY;
+ }
+ int finalAlpha = Color.alpha(colorFromAnimations) * parentAlpha / 255;
+ if (finalAlpha != lastAlpha || hasDirtyFlags) {
+ paint.setColor(finalAlpha << 24 | (colorFromAnimations & 0xFFFFFF));
+ }
+ lastAlpha = finalAlpha;
if (colorFilterAnimation != null) {
paint.setColorFilter(colorFilterAnimation.getValue());
@@ -120,16 +149,34 @@
dropShadowAnimation.applyTo(paint);
}
- path.reset();
- for (int i = 0; i < paths.size(); i++) {
- path.addPath(paths.get(i).getPath(), parentMatrix);
- }
-
+ updatePath(parentMatrix);
canvas.drawPath(path, paint);
-
L.endSection("FillContent#draw");
}
+ private void updatePath(Matrix parentMatrix) {
+ int pathGeneration = pathGeneration();
+ if (this.pathGeneration != pathGeneration || !parentMatrix.equals(pathWithParentMatrixMatrix)) {
+ path.rewind();
+ for (int i = 0; i < paths.size(); i++) {
+ Path newPath = paths.get(i).getPath();
+ path.addPath(newPath);
+ }
+ path.transform(parentMatrix);
+ this.pathGeneration = pathGeneration;
+ pathWithParentMatrixMatrix.set(parentMatrix);
+ }
+ }
+
+ private int pathGeneration() {
+ int result = 0;
+ for (int i = paths.size() - 1; i >= 0; i--) {
+ PathContent content = paths.get(i);
+ result = 31 * result + content.getGeneration();
+ }
+ return result;
+ }
+
@Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
path.reset();
for (int i = 0; i < paths.size(); i++) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/MergePathsContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/MergePathsContent.java
index b69d713..5ad5388 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/MergePathsContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/MergePathsContent.java
@@ -76,6 +76,15 @@
return path;
}
+ @Override public int getGeneration() {
+ int result = 0;
+ for (int i = pathContents.size() - 1; i >= 0; i--) {
+ PathContent content = pathContents.get(i);
+ result = 31 * result + content.getGeneration();
+ }
+ return result;
+ }
+
@Override public String getName() {
return name;
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/PathContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/PathContent.java
index c7f6bfc..5e4736e 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/PathContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/PathContent.java
@@ -4,4 +4,5 @@
interface PathContent extends Content {
Path getPath();
+ int getGeneration();
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java
index cfa9523..ec05d44 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java
@@ -44,6 +44,7 @@
private final CompoundTrimPathContent trimPaths = new CompoundTrimPathContent();
private boolean isPathValid;
+ private int generation;
public PolystarContent(LottieDrawable lottieDrawable, BaseLayer layer,
PolystarShape polystarShape) {
@@ -93,6 +94,7 @@
private void invalidate() {
isPathValid = false;
+ generation++;
lottieDrawable.invalidateSelf();
}
@@ -137,6 +139,10 @@
return path;
}
+ @Override public int getGeneration() {
+ return generation;
+ }
+
@Override public String getName() {
return name;
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/RectangleContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/RectangleContent.java
index 89a966f..a883397 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/RectangleContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/RectangleContent.java
@@ -35,6 +35,7 @@
/** This corner radius is from a layer item. The first one is from the roundedness on this specific rect. */
@Nullable private BaseKeyframeAnimation<Float, Float> roundedCornersAnimation = null;
private boolean isPathValid;
+ private int generation = 0;
public RectangleContent(LottieDrawable lottieDrawable, BaseLayer layer, RectangleShape rectShape) {
name = rectShape.getName();
@@ -65,6 +66,7 @@
private void invalidate() {
isPathValid = false;
+ generation++;
lottieDrawable.invalidateSelf();
}
@@ -161,6 +163,10 @@
return path;
}
+ @Override public int getGeneration() {
+ return generation;
+ }
+
@Override
public void resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator,
KeyPath currentPartialKeyPath) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/RepeaterContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/RepeaterContent.java
index 4a721fb..82a57b9 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/RepeaterContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/RepeaterContent.java
@@ -105,6 +105,11 @@
return path;
}
+ @Override
+ public int getGeneration() {
+ return contentGroup.getGeneration();
+ }
+
@Override public void draw(Canvas canvas, Matrix parentMatrix, int alpha) {
float copies = this.copies.getValue();
float offset = this.offset.getValue();
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
index 1f54ac0..7b679cd 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
@@ -24,6 +24,7 @@
@Nullable private List<ShapeModifierContent> shapeModifierContents;
private boolean isPathValid;
+ private int generation = 0;
private final CompoundTrimPathContent trimPaths = new CompoundTrimPathContent();
public ShapeContent(LottieDrawable lottieDrawable, BaseLayer layer, ShapePath shape) {
@@ -41,6 +42,7 @@
private void invalidate() {
isPathValid = false;
+ generation++;
lottieDrawable.invalidateSelf();
}
@@ -91,6 +93,10 @@
return path;
}
+ @Override public int getGeneration() {
+ return generation;
+ }
+
@Override public String getName() {
return name;
}