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; }