Masks work
diff --git a/issue-repro/src/main/res/raw/anim.json b/issue-repro/src/main/res/raw/anim.json
index 8ac014b..5cecbc8 100644
--- a/issue-repro/src/main/res/raw/anim.json
+++ b/issue-repro/src/main/res/raw/anim.json
@@ -1 +1 @@
-{"v":"5.7.4","fr":60,"ip":0,"op":181,"w":400,"h":400,"nm":"Diagonal","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[150,150],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-125,-125],"to":[41.667,41.667],"ti":[-41.667,-41.667]},{"t":180,"s":[125,125]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
+{"v":"5.7.4","fr":60,"ip":0,"op":300,"w":400,"h":400,"nm":"Matte","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[150,150],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[75,75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":181,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeLottieTransform.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeLottieTransform.kt
index ece86b3..1dcafdb 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeLottieTransform.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeLottieTransform.kt
@@ -10,18 +10,6 @@
import com.airbnb.lottie.value.Keyframe
import kotlin.properties.Delegates
-fun Modifier.withTransform(transform: ComposeLottieTransform): Modifier {
- if (transform.isIdentity) return this
-
- // TODO: maybe use composed {}
- return this.then(
- Modifier.graphicsLayer(
- translationX = transform.position.x,
- translationY = transform.position.y,
- )
- )
-}
-
@Composable
fun rememberTransform(layer: Layer): ComposeLottieTransform {
val animatableTransform = remember(layer) { layer.transform }
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeUtils.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeUtils.kt
index 56a2865..19b7a09 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeUtils.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/ComposeUtils.kt
@@ -2,6 +2,7 @@
import android.graphics.PointF
import androidx.annotation.FloatRange
+import com.airbnb.lottie.model.content.ShapeData
import kotlin.math.roundToInt
fun lerp(a: Float, b: Float, @FloatRange(from = 0.0, to = 1.0) percentage: Float): Float = a + percentage * (b - a)
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/LottieComposeRenderer.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/LottieComposeRenderer.kt
index c50b0d8..58b0222 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/LottieComposeRenderer.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/renderer/LottieComposeRenderer.kt
@@ -2,6 +2,11 @@
import android.graphics.PointF
import android.util.Log
+import androidx.compose.animation.animate
+import androidx.compose.animation.core.DurationBasedAnimationSpec
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.foundation.Image
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@@ -12,10 +17,9 @@
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.compose.LottieAnimationState
import com.airbnb.lottie.compose.LottieCompositionResult
-import com.airbnb.lottie.model.content.RectangleShape
-import com.airbnb.lottie.model.content.ShapeFill
-import com.airbnb.lottie.model.content.ShapeGroup
+import com.airbnb.lottie.model.content.*
import com.airbnb.lottie.model.layer.Layer
+import com.airbnb.lottie.value.Keyframe
import java.util.concurrent.TimeUnit
import kotlin.math.floor
@@ -81,20 +85,20 @@
viewportWidth = composition.bounds.width().toFloat(),
viewportHeight = composition.bounds.height().toFloat(),
) { viewportWidth, viewportHeight ->
- Log.d("Gabe", "shapeLayerPainter ${transform.position}")
+ val clipPathData = getMaskPathData(layer.masks)
+ Log.d("Gabe", "clipPathData hash ${clipPathData.hashCode()} identity ${System.identityHashCode(clipPathData)}")
Group(
name = layer.layerName,
translationX = transform.position.x,
translationY = transform.position.y,
scaleX = composition.bounds.width() / viewportWidth,
scaleY = composition.bounds.height() / viewportHeight,
+ clipPathData = clipPathData,
) {
- PathData {
- layer.shapes.forEach { shapeModel ->
- when (shapeModel) {
- is ShapeGroup -> ComposeShapeGroup(shapeModel)
- else -> Log.d("Gabe", "Don't know how to draw ${shapeModel::class.simpleName}")
- }
+ layer.shapes.forEach { shapeModel ->
+ when (shapeModel) {
+ is ShapeGroup -> ComposeShapeGroup(shapeModel)
+ else -> Log.d("Gabe", "Don't know how to draw ${shapeModel::class.simpleName}")
}
}
}
@@ -106,8 +110,6 @@
if (shapeGroup.isHidden || shapeGroup.items.isEmpty()) return
val transform = rememberTransform(shapeGroup)
val pathData = remember { mutableListOf<PathNode>() }
-
- Log.d("Gabe", "ComposeShapeGroup ${transform.position}")
Group(
name = shapeGroup.name,
translationX = transform.position.x,
@@ -138,18 +140,11 @@
@Composable
fun rectanglePathData(shape: RectangleShape): List<PathNode> {
- val progress = LocalLottieProgress.current
- // TODO: optimize this
val size = remember { PointF() }
- val sizeKeyframe = shape.size.keyframes.firstOrNull { it.containsProgress(progress) } ?: return emptyList()
- val linearSizeProgress = lerp(sizeKeyframe.startProgress, sizeKeyframe.endProgress, progress)
- val interpolatedProgress = when (val i = sizeKeyframe.interpolator) {
- null -> linearSizeProgress
- else -> i.getInterpolation(linearSizeProgress)
- }
+ val (sizeKeyframe, interpolatedProgress) = shape.size.keyframes.rememberKeyframeProgress()
size.set(
- lerp(sizeKeyframe.startValue?.x ?: 0f, sizeKeyframe.endValue?.x ?: 0f, interpolatedProgress),
- lerp(sizeKeyframe.startValue?.y ?: 0f, sizeKeyframe.endValue?.y ?: 0f, interpolatedProgress),
+ lerp(sizeKeyframe?.startValue?.x ?: 0f, sizeKeyframe?.endValue?.x ?: 0f, interpolatedProgress),
+ lerp(sizeKeyframe?.startValue?.y ?: 0f, sizeKeyframe?.endValue?.y ?: 0f, interpolatedProgress),
)
val halfWidth = size.x / 2f
@@ -167,11 +162,62 @@
@Composable
fun ComposeShapeFill(fill: ShapeFill, pathData: List<PathNode>) {
+ // TODO
val colorAnimation = fill.color?.keyframes ?: return
-
Path(
pathData,
// TODO: use the real color
fill = SolidColor(Color.Red),
)
+}
+
+@Composable
+fun getMaskPathData(masks: List<Mask>): List<PathNode> {
+ // TODO: figure out how to reuse the mutable list.
+ // https://issuetracker.google.com/issues/180774141
+ val pathNodes = mutableListOf<PathNode>()
+ val allShapeData = remember(masks.size) { List(masks.size) { ShapeData() } }
+ pathNodes.clear()
+ for (i in masks.indices) {
+ val mask = masks[i]
+ val (keyframe, progress) = mask.maskPath.keyframes.rememberKeyframeProgress()
+ val shapeData = allShapeData[i]
+ val startData = keyframe?.startValue
+ val endData = keyframe?.endValue
+ if (startData != null && endData != null) {
+ shapeData.interpolateBetween(startData, endData, progress)
+ pathNodes += PathData {
+ moveTo(shapeData.initialPoint.x, shapeData.initialPoint.y)
+ for (curveData in shapeData.curves) {
+ curveTo(
+ curveData.controlPoint1.x,
+ curveData.controlPoint1.y,
+ curveData.controlPoint2.x,
+ curveData.controlPoint2.y,
+ curveData.vertex.x,
+ curveData.vertex.y,
+ )
+ }
+ }
+ }
+ }
+ return pathNodes
+}
+
+data class KeyframeProgress<T>(var keyframe: Keyframe<T>? = null, var progress: Float = 0f)
+
+@Composable
+fun <T> List<Keyframe<T>>.rememberKeyframeProgress(): KeyframeProgress<T> {
+ val progress = LocalLottieProgress.current
+ val value = remember { KeyframeProgress<T>() }
+
+ val keyframe = firstOrNull { it.containsProgress(progress) } ?: return value
+ value.keyframe = keyframe
+ val linearSizeProgress = lerp(keyframe.startProgress, keyframe.endProgress, progress)
+ value.progress = when (val i = keyframe.interpolator) {
+ null -> linearSizeProgress
+ else -> i.getInterpolation(linearSizeProgress)
+ }
+
+ return value
}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
index fd4aa09..fe4e940 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/Layer.java
@@ -125,7 +125,7 @@
return preCompHeight;
}
- List<Mask> getMasks() {
+ public List<Mask> getMasks() {
return masks;
}