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