WIP mattes
diff --git a/issue-repro/src/main/java/com/airbnb/lottie/issues/IssueReproActivity.kt b/issue-repro/src/main/java/com/airbnb/lottie/issues/IssueReproActivity.kt
index 80f04ca..ce93001 100755
--- a/issue-repro/src/main/java/com/airbnb/lottie/issues/IssueReproActivity.kt
+++ b/issue-repro/src/main/java/com/airbnb/lottie/issues/IssueReproActivity.kt
@@ -1,6 +1,7 @@
 package com.airbnb.lottie.issues
 
 import android.os.Bundle
+import android.util.Log
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.foundation.background
diff --git a/issue-repro/src/main/res/raw/anim.json b/issue-repro/src/main/res/raw/anim.json
index 5cecbc8..b7691a7 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":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
+{"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 2","td":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},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","tt":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":[50,50],"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 1dcafdb..4ab4337 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
@@ -11,21 +11,22 @@
 import kotlin.properties.Delegates
 
 @Composable
-fun rememberTransform(layer: Layer): ComposeLottieTransform {
+fun rememberTransform(layer: Layer, progress: Float): ComposeLottieTransform {
     val animatableTransform = remember(layer) { layer.transform }
-    return rememberTransform(animatableTransform)
+    return rememberTransform(animatableTransform, progress)
 }
 
 @Composable
-fun rememberTransform(shapeGroup: ShapeGroup): ComposeLottieTransform {
+fun rememberTransform(shapeGroup: ShapeGroup, progress: Float): ComposeLottieTransform {
     val animatableTransform = remember(shapeGroup) { shapeGroup.items.lastOrNull() as? AnimatableTransform }
-    return rememberTransform(animatableTransform)
+    return rememberTransform(animatableTransform, progress)
 }
 
 @Composable
-fun rememberTransform(transform: AnimatableTransform?): ComposeLottieTransform {
+fun rememberTransform(transform: AnimatableTransform?, progress: Float): ComposeLottieTransform {
+    // TODO: look into optimizations here.
     val composeTransform = remember(transform) { ComposeLottieTransform(transform) }
-    composeTransform.progress = LocalLottieProgress.current
+    composeTransform.progress = progress
     return composeTransform
 }
 
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 58b0222..6820d14 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,16 +2,15 @@
 
 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.animation.core.*
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.vector.*
 import androidx.compose.ui.unit.dp
 import com.airbnb.lottie.LottieComposition
@@ -21,10 +20,9 @@
 import com.airbnb.lottie.model.layer.Layer
 import com.airbnb.lottie.value.Keyframe
 import java.util.concurrent.TimeUnit
+import kotlin.math.ceil
 import kotlin.math.floor
 
-val LocalLottieProgress = compositionLocalOf { 0f }
-
 @Composable
 fun ComposeLottieAnimation(
     compositionResult: LottieCompositionResult,
@@ -60,33 +58,65 @@
         }
     }
 
-    Providers(
-        LocalLottieProgress provides state.progress
-    ) {
+    Box(modifier = Modifier.then(modifier)) {
+        var matteLayer: Layer? = null
         composition.layers.forEach { layer ->
             when (layer.layerType) {
-                Layer.LayerType.SHAPE -> Image(
-                    shapeLayerPainter(composition, layer),
-                    contentDescription = null,
-                    modifier = modifier,
-                )
+                Layer.LayerType.SHAPE -> {
+                    if (layer.matteType != Layer.MatteType.NONE) {
+                        matteLayer = layer
+                    } else {
+                        ShapeLayer(composition, layer, matteLayer, state.progress)
+                    }
+                }
                 else -> Unit
             }
+            matteLayer = null
+        }
+    }
+}
+
+@Composable
+fun ShapeLayer(composition: LottieComposition, layer: Layer, matteLayer: Layer?, progress: Float) {
+    val layerPainter = shapeLayerPainter(composition, layer, progress)
+    val matteLayerPainter = if (matteLayer == null) null else shapeLayerPainter(composition, layer, progress)
+    Canvas(
+        modifier = Modifier
+    ) {
+        with(layerPainter) {
+            // TODO: cache this Size class.
+            draw(Size(composition.bounds.width().toFloat(), composition.bounds.height().toFloat()))
+        }
+        if (matteLayerPainter != null) {
+            drawIntoCanvas { canvas ->
+                // TODO: only save the right bounds.
+                val mattePaint = Paint()
+                mattePaint.blendMode = BlendMode.DstOut
+                canvas.withSaveLayer(
+                    Rect(0f, 0f, composition.bounds.width().toFloat(), composition.bounds.height().toFloat()),
+                    mattePaint,
+                ) {
+                    with(matteLayerPainter) {
+                        // TODO: cache this Size class.
+                        draw(Size(composition.bounds.width().toFloat(), composition.bounds.height().toFloat()))
+                    }
+                }
+            }
         }
     }
 }
 
 @Composable
-fun shapeLayerPainter(composition: LottieComposition, layer: Layer): VectorPainter {
-    val transform = rememberTransform(layer)
+fun shapeLayerPainter(composition: LottieComposition, layer: Layer, progress: Float): VectorPainter {
+    val transform = rememberTransform(layer, progress)
     return rememberVectorPainter(
         defaultWidth = composition.bounds.width().dp,
         defaultHeight = composition.bounds.height().dp,
         viewportWidth = composition.bounds.width().toFloat(),
         viewportHeight = composition.bounds.height().toFloat(),
     ) { viewportWidth, viewportHeight ->
-        val clipPathData = getMaskPathData(layer.masks)
-        Log.d("Gabe", "clipPathData hash ${clipPathData.hashCode()} identity ${System.identityHashCode(clipPathData)}")
+        val clipPathData = getMaskPathData(layer.masks, progress)
+
         Group(
             name = layer.layerName,
             translationX = transform.position.x,
@@ -97,8 +127,7 @@
         ) {
             layer.shapes.forEach { shapeModel ->
                 when (shapeModel) {
-                    is ShapeGroup -> ComposeShapeGroup(shapeModel)
-                    else -> Log.d("Gabe", "Don't know how to draw ${shapeModel::class.simpleName}")
+                    is ShapeGroup -> ComposeShapeGroup(shapeModel, progress)
                 }
             }
         }
@@ -106,10 +135,10 @@
 }
 
 @Composable
-fun ComposeShapeGroup(shapeGroup: ShapeGroup) {
+fun ComposeShapeGroup(shapeGroup: ShapeGroup, progress: Float) {
     if (shapeGroup.isHidden || shapeGroup.items.isEmpty()) return
-    val transform = rememberTransform(shapeGroup)
-    val pathData = remember { mutableListOf<PathNode>() }
+    val transform = rememberTransform(shapeGroup, progress)
+    val pathData = remember { mutableStateListOf<PathNode>() }
     Group(
         name = shapeGroup.name,
         translationX = transform.position.x,
@@ -117,18 +146,10 @@
     ) {
         // Reuse the list and clear it so that the backing array doesn't need to be recreated.
         pathData.clear()
-        pathData += PathData {
-            moveTo(380f, 0f)
-            lineTo(400f, 0f)
-            lineTo(400f, 20f)
-            lineTo(380f, 20f)
-            lineTo(380f, 0f)
-            close()
-        }
         for (model in shapeGroup.items) {
             when (model) {
                 is RectangleShape -> {
-                    pathData += rectanglePathData(model)
+                    pathData += rectanglePathData(model, progress)
                 }
                 is ShapeFill -> {
                     ComposeShapeFill(model, pathData)
@@ -139,9 +160,9 @@
 }
 
 @Composable
-fun rectanglePathData(shape: RectangleShape): List<PathNode> {
+fun rectanglePathData(shape: RectangleShape, progress: Float): List<PathNode> {
     val size = remember { PointF() }
-    val (sizeKeyframe, interpolatedProgress) = shape.size.keyframes.rememberKeyframeProgress()
+    val (sizeKeyframe, interpolatedProgress) = shape.size.keyframes.rememberKeyframeProgress(progress)
     size.set(
         lerp(sizeKeyframe?.startValue?.x ?: 0f, sizeKeyframe?.endValue?.x ?: 0f, interpolatedProgress),
         lerp(sizeKeyframe?.startValue?.y ?: 0f, sizeKeyframe?.endValue?.y ?: 0f, interpolatedProgress),
@@ -172,7 +193,7 @@
 }
 
 @Composable
-fun getMaskPathData(masks: List<Mask>): List<PathNode> {
+fun getMaskPathData(masks: List<Mask>, progress: Float): List<PathNode> {
     // TODO: figure out how to reuse the mutable list.
     // https://issuetracker.google.com/issues/180774141
     val pathNodes = mutableListOf<PathNode>()
@@ -180,12 +201,12 @@
     pathNodes.clear()
     for (i in masks.indices) {
         val mask = masks[i]
-        val (keyframe, progress) = mask.maskPath.keyframes.rememberKeyframeProgress()
+        val (keyframe, keyframeProgress) = mask.maskPath.keyframes.rememberKeyframeProgress(progress)
         val shapeData = allShapeData[i]
         val startData = keyframe?.startValue
         val endData = keyframe?.endValue
         if (startData != null && endData != null) {
-            shapeData.interpolateBetween(startData, endData, progress)
+            shapeData.interpolateBetween(startData, endData, keyframeProgress)
             pathNodes += PathData {
                 moveTo(shapeData.initialPoint.x, shapeData.initialPoint.y)
                 for (curveData in shapeData.curves) {
@@ -207,8 +228,7 @@
 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
+fun <T> List<Keyframe<T>>.rememberKeyframeProgress(progress: Float): KeyframeProgress<T> {
     val value = remember { KeyframeProgress<T>() }
 
     val keyframe = firstOrNull { it.containsProgress(progress) } ?: return value
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
index f287299..d009f8f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
@@ -146,6 +146,11 @@
     invalidateSelf();
   }
 
+  @Nullable
+  public BaseLayer getMatteLayer() {
+    return matteLayer;
+  }
+
   Layer getLayerModel() {
     return layerModel;
   }
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 fe4e940..32abfe0 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
@@ -137,7 +137,7 @@
     return layerName;
   }
 
-  MatteType getMatteType() {
+  public MatteType getMatteType() {
     return matteType;
   }
 
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
index 37e48e2..17b70c4 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
@@ -84,7 +84,6 @@
         if (state.currentPage >= state.lastPage) return@withState
         fetchJob = viewModelScope.launch {
             val response = try {
-                Log.d("Gabe", "Fetching page ${state.currentPage + 1}")
                 api.search(state.query, state.currentPage + 1)
             } catch (e: Exception) {
                 setState { copy(fetchException = true) }