From c5b8318c7cf205e95db143955acbfc69f86bc339 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Sat, 15 Jan 2022 21:45:39 -0800 Subject: [PATCH] Add an API to always render images at the original size (#1706) This allows apps that set custom images to provide images that are smaller than the original image to save memory. Fixes #1968 --- .../airbnb/lottie/compose/LottieAnimation.kt | 10 +++ .../lottie/compose/LottieDynamicProperties.kt | 1 + .../airbnb/lottie/LottieAnimationView.java | 20 +++++ .../com/airbnb/lottie/LottieDrawable.java | 65 ++++++++++++++ .../com/airbnb/lottie/LottieImageAsset.java | 5 +- .../lottie/manager/ImageAssetManager.java | 4 + .../airbnb/lottie/model/layer/ImageLayer.java | 31 +++++-- .../snapshots/SnapshotTestCaseContext.kt | 82 ++++++++++-------- .../tests/ComposeDynamicPropertiesTestCase.kt | 53 ++++++++++- .../src/main/assets/Images/Heart-1200.png | Bin 0 -> 30430 bytes .../src/main/assets/Images/Heart-80.png | Bin 0 -> 3437 bytes .../src/main/assets/Tests/Heart.json | 1 + .../airbnb/lottie/snapshots/FilmStripView.kt | 2 + 13 files changed, 227 insertions(+), 47 deletions(-) create mode 100644 snapshot-tests/src/main/assets/Images/Heart-1200.png create mode 100644 snapshot-tests/src/main/assets/Images/Heart-80.png create mode 100644 snapshot-tests/src/main/assets/Tests/Heart.json diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt index ed4b10c5e..01a3c6a3b 100644 --- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt +++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt @@ -54,6 +54,12 @@ import kotlin.math.roundToInt * features so it defaults to off. The only way to know if your animation will work * well with merge paths or not is to try it. If your animation has merge paths and * doesn't render correctly, please file an issue. + * @param renderMode Allows you to specify whether you want Lottie to use hardware or software rendering. + * Defaults to AUTOMATIC. Refer to [LottieAnimationView.setRenderMode] for more info. + * @param maintainOriginalImageBounds When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, + * regardless of the bitmap size. + * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. + * Defaults to false. * @param dynamicProperties Allows you to change the properties of an animation dynamically. To use them, use * [rememberLottieDynamicProperties]. Refer to its docs for more info. * @param alignment Define where the animation should be placed within this composable if it has a different @@ -70,6 +76,7 @@ fun LottieAnimation( applyOpacityToLayers: Boolean = false, enableMergePaths: Boolean = false, renderMode: RenderMode = RenderMode.AUTOMATIC, + maintainOriginalImageBounds: Boolean = false, dynamicProperties: LottieDynamicProperties? = null, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, @@ -108,6 +115,7 @@ fun LottieAnimation( drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers drawable.enableMergePathsForKitKatAndAbove(enableMergePaths) drawable.useSoftwareRendering(useSoftwareRendering) + drawable.maintainOriginalImageBounds = maintainOriginalImageBounds drawable.clipToCompositionBounds = clipToComposition drawable.progress = progress drawable.setBounds(0, 0, composition.bounds.width(), composition.bounds.height()) @@ -136,6 +144,7 @@ fun LottieAnimation( applyOpacityToLayers: Boolean = false, enableMergePaths: Boolean = false, renderMode: RenderMode = RenderMode.AUTOMATIC, + maintainOriginalImageBounds: Boolean = false, dynamicProperties: LottieDynamicProperties? = null, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, @@ -157,6 +166,7 @@ fun LottieAnimation( applyOpacityToLayers, enableMergePaths, renderMode, + maintainOriginalImageBounds, dynamicProperties, alignment, contentScale, diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt index 3bec2455d..ea1331c84 100644 --- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt +++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt @@ -19,6 +19,7 @@ import com.airbnb.lottie.value.ScaleXY * This takes a vararg of individual dynamic properties which should be created with [rememberLottieDynamicProperty]. * * @see rememberLottieDynamicProperty + * @see LottieDrawable.setMaintainOriginalImageBounds */ @Composable fun rememberLottieDynamicProperties( diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java index bd8e56097..20d30cf8a 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java @@ -843,6 +843,26 @@ public String getImageAssetsFolder() { return lottieDrawable.getImageAssetsFolder(); } + /** + * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. + * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. + * + * Defaults to false. + */ + public void setMaintainOriginalImageBounds(boolean maintainOriginalImageBounds) { + lottieDrawable.setMaintainOriginalImageBounds(maintainOriginalImageBounds); + } + + /** + * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. + * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. + * + * Defaults to false. + */ + public boolean getMaintainOriginalImageBounds() { + return lottieDrawable.getMaintainOriginalImageBounds(); + } + /** * Allows you to modify or clear a bitmap that was loaded for an image either automatically * through {@link #setImageAssetsFolder(String)} or with an {@link ImageAssetDelegate}. diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java index d801f8009..10303c791 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java @@ -112,6 +112,7 @@ public void onAnimationUpdate(ValueAnimator animation) { @Nullable TextDelegate textDelegate; private boolean enableMergePaths; + private boolean maintainOriginalImageBounds = false; private boolean clipToCompositionBounds = true; @Nullable private CompositionLayer compositionLayer; @@ -254,6 +255,26 @@ public String getImageAssetsFolder() { return imageAssetsFolder; } + /** + * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. + * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. + * + * Defaults to false. + */ + public void setMaintainOriginalImageBounds(boolean maintainOriginalImageBounds) { + this.maintainOriginalImageBounds = maintainOriginalImageBounds; + } + + /** + * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. + * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. + * + * Defaults to false. + */ + public boolean getMaintainOriginalImageBounds() { + return maintainOriginalImageBounds; + } + /** * Create a composition with {@link LottieCompositionFactory} * @@ -1115,7 +1136,11 @@ public Bitmap updateBitmap(String id, @Nullable Bitmap bitmap) { return ret; } + /** + * @deprecated use {@link #getBitmapForId(String)}. + */ @Nullable + @Deprecated public Bitmap getImageAsset(String id) { ImageAssetManager bm = getImageAssetManager(); if (bm != null) { @@ -1128,6 +1153,46 @@ public Bitmap getImageAsset(String id) { return null; } + /** + * Returns the bitmap that will be rendered for the given id in the Lottie animation file. + * The id is the asset reference id stored in the "id" property of each object in the "assets" array. + * + * The returned bitmap could be from: + * * Embedded in the animation file as a base64 string. + * * In the same directory as the animation file. + * * In the same zip file as the animation file. + * * Returned from an {@link ImageAssetDelegate}. + * or null if the image doesn't exist from any of those places. + */ + @Nullable + public Bitmap getBitmapForId(String id) { + ImageAssetManager assetManager = getImageAssetManager(); + if (assetManager != null) { + return assetManager.bitmapForId(id); + } + return null; + } + + /** + * Returns the {@link LottieImageAsset} that will be rendered for the given id in the Lottie animation file. + * The id is the asset reference id stored in the "id" property of each object in the "assets" array. + * + * The returned bitmap could be from: + * * Embedded in the animation file as a base64 string. + * * In the same directory as the animation file. + * * In the same zip file as the animation file. + * * Returned from an {@link ImageAssetDelegate}. + * or null if the image doesn't exist from any of those places. + */ + @Nullable + public LottieImageAsset getLottieImageAssetForId(String id) { + LottieComposition composition = this.composition; + if (composition == null) { + return null; + } + return composition.getImages().get(id); + } + private ImageAssetManager getImageAssetManager() { if (getCallback() == null) { // We can't get a bitmap since we can't get a Context from the callback. diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java index f1acbe66c..39606060a 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java @@ -6,7 +6,7 @@ import androidx.annotation.RestrictTo; /** - * Data class describing an image asset exported by bodymovin. + * Data class describing an image asset embedded in a Lottie json file. */ public class LottieImageAsset { private final int width; @@ -36,6 +36,9 @@ public int getHeight() { return height; } + /** + * The reference id in the json file. + */ public String getId() { return id; } diff --git a/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java b/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java index d28061d8c..063435704 100644 --- a/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java +++ b/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java @@ -62,6 +62,10 @@ public void setDelegate(@Nullable ImageAssetDelegate assetDelegate) { return prevBitmap; } + @Nullable public LottieImageAsset getImageAssetById(String id) { + return imageAssets.get(id); + } + @Nullable public Bitmap bitmapForId(String id) { LottieImageAsset asset = imageAssets.get(id); if (asset == null) { diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java index 16534495f..6cd60b648 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import com.airbnb.lottie.LottieDrawable; +import com.airbnb.lottie.LottieImageAsset; import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.animation.LPaint; import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation; @@ -24,16 +25,18 @@ public class ImageLayer extends BaseLayer { private final Paint paint = new LPaint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Rect src = new Rect(); private final Rect dst = new Rect(); + @Nullable private final LottieImageAsset lottieImageAsset; @Nullable private BaseKeyframeAnimation colorFilterAnimation; @Nullable private BaseKeyframeAnimation imageAnimation; ImageLayer(LottieDrawable lottieDrawable, Layer layerModel) { super(lottieDrawable, layerModel); + lottieImageAsset = lottieDrawable.getLottieImageAssetForId(layerModel.getRefId()); } @Override public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) { Bitmap bitmap = getBitmap(); - if (bitmap == null || bitmap.isRecycled()) { + if (bitmap == null || bitmap.isRecycled() || lottieImageAsset == null) { return; } float density = Utils.dpScale(); @@ -45,16 +48,21 @@ public class ImageLayer extends BaseLayer { canvas.save(); canvas.concat(parentMatrix); src.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); - dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density)); + if (lottieDrawable.getMaintainOriginalImageBounds()) { + dst.set(0, 0, (int) (lottieImageAsset.getWidth() * density), (int) (lottieImageAsset.getHeight() * density)); + } else { + dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density)); + } + canvas.drawBitmap(bitmap, src, dst, paint); canvas.restore(); } @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) { super.getBounds(outBounds, parentMatrix, applyParents); - Bitmap bitmap = getBitmap(); - if (bitmap != null) { - outBounds.set(0, 0, bitmap.getWidth() * Utils.dpScale(), bitmap.getHeight() * Utils.dpScale()); + if (lottieImageAsset != null) { + float scale = Utils.dpScale(); + outBounds.set(0, 0, lottieImageAsset.getWidth() * scale, lottieImageAsset.getHeight() * scale); boundsMatrix.mapRect(outBounds); } } @@ -63,11 +71,20 @@ public class ImageLayer extends BaseLayer { private Bitmap getBitmap() { if (imageAnimation != null) { Bitmap callbackBitmap = imageAnimation.getValue(); - if (callbackBitmap != null) + if (callbackBitmap != null) { return callbackBitmap; + } } String refId = layerModel.getRefId(); - return lottieDrawable.getImageAsset(refId); + Bitmap bitmapFromDrawable = lottieDrawable.getBitmapForId(refId); + if (bitmapFromDrawable != null) { + return bitmapFromDrawable; + } + LottieImageAsset asset = this.lottieImageAsset; + if (asset != null) { + return asset.getBitmap(); + } + return null; } @SuppressWarnings("SingleStatementInBlock") diff --git a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/SnapshotTestCaseContext.kt b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/SnapshotTestCaseContext.kt index b7e4e4be5..b05d0b0c9 100644 --- a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/SnapshotTestCaseContext.kt +++ b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/SnapshotTestCaseContext.kt @@ -9,10 +9,13 @@ import android.graphics.PorterDuff import android.util.Log import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.platform.ComposeView import androidx.core.view.doOnLayout import com.airbnb.lottie.FontAssetDelegate @@ -26,13 +29,12 @@ import com.airbnb.lottie.snapshots.utils.BitmapPool import com.airbnb.lottie.snapshots.utils.HappoSnapshotter import com.airbnb.lottie.snapshots.utils.ObjectPool import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withContext import kotlin.coroutines.resume -private val ActivityContentLock = Mutex() - /** * Set of properties that are available to all [SnapshotTestCase] runs. */ @@ -167,6 +169,12 @@ suspend fun SnapshotTestCaseContext.snapshotComposition( bitmapPool.release(bitmap) } +/** + * Use this to signal that the composition is not ready to be snapshot yet. + * This use useful if you are using things like `rememberLottieComposition` which parses a composition asynchronously. + */ +val LocalSnapshotReady = compositionLocalOf { MutableStateFlow(true) } + fun SnapshotTestCaseContext.loadCompositionFromAssetsSync(fileName: String): LottieComposition { return LottieCompositionFactory.fromAssetSync(context, fileName).value!! } @@ -180,53 +188,55 @@ suspend fun SnapshotTestCaseContext.snapshotComposable( log("Snapshotting $name") val composeView = ComposeView(context) composeView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) - var bitmap = withContext(Dispatchers.Main) { - composeView.setContent { + val readyFlow = MutableStateFlow(null) + composeView.setContent { + CompositionLocalProvider(LocalSnapshotReady provides readyFlow) { content(RenderMode.SOFTWARE) } - suspendCancellableCoroutine { cont -> - composeView.doOnLayout { - log("Drawing $name") - val b = bitmapPool.acquire(composeView.width, composeView.height) - val canvas = Canvas(b) - composeView.draw(canvas) - cont.resume(b) - } - onActivity { activity -> - activity.binding.content.addView(composeView) - } - } + if (readyFlow.value == null) readyFlow.value = true } onActivity { activity -> - activity.binding.content.removeView(composeView) + activity.binding.content.addView(composeView) } + readyFlow.first { it == true } + composeView.awaitFrame() + log("Drawing $name - Software") + var bitmap = bitmapPool.acquire(composeView.width, composeView.height) + var canvas = Canvas(bitmap) + composeView.draw(canvas) snapshotter.record(bitmap, name, if (renderHardwareAndSoftware) "$variant - Software" else variant) bitmapPool.release(bitmap) if (renderHardwareAndSoftware) { - bitmap = withContext(Dispatchers.Main) { - composeView.setContent { + readyFlow.value = null + composeView.setContent { + CompositionLocalProvider(LocalSnapshotReady provides readyFlow) { content(RenderMode.HARDWARE) } - suspendCancellableCoroutine { cont -> - composeView.doOnLayout { - log("Drawing $name") - val b = bitmapPool.acquire(composeView.width, composeView.height) - val canvas = Canvas(b) - composeView.draw(canvas) - cont.resume(b) - } - onActivity { activity -> - activity.binding.content.addView(composeView) - } - } + if (readyFlow.value == null) readyFlow.value = true } - onActivity { activity -> - activity.binding.content.removeView(composeView) - } - snapshotter.record(bitmap, name, "$variant - Hardware") + readyFlow.first { it == true } + composeView.awaitFrame() + log("Drawing $name - Software") + bitmap = bitmapPool.acquire(composeView.width, composeView.height) + canvas = Canvas(bitmap) + composeView.draw(canvas) + snapshotter.record(bitmap, name, if (renderHardwareAndSoftware) "$variant - Hardware" else variant) bitmapPool.release(bitmap) } + + onActivity { activity -> + activity.binding.content.removeView(composeView) + } + LottieCompositionCache.getInstance().clear() +} + +private suspend fun View.awaitFrame() { + suspendCancellableCoroutine { cont -> + post { + cont.resume(Unit) + } + } } \ No newline at end of file diff --git a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/ComposeDynamicPropertiesTestCase.kt b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/ComposeDynamicPropertiesTestCase.kt index 6356baf9d..a4b9be7ca 100644 --- a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/ComposeDynamicPropertiesTestCase.kt +++ b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/ComposeDynamicPropertiesTestCase.kt @@ -1,21 +1,25 @@ package com.airbnb.lottie.snapshots.tests +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Color -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.getValue import com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.rememberLottieComposition import com.airbnb.lottie.compose.rememberLottieDynamicProperties import com.airbnb.lottie.compose.rememberLottieDynamicProperty +import com.airbnb.lottie.snapshots.LocalSnapshotReady import com.airbnb.lottie.snapshots.SnapshotTestCase import com.airbnb.lottie.snapshots.SnapshotTestCaseContext import com.airbnb.lottie.snapshots.snapshotComposable -import com.airbnb.lottie.snapshots.snapshotComposition class ComposeDynamicPropertiesTestCase : SnapshotTestCase { override suspend fun SnapshotTestCaseContext.run() { - val composition = LottieCompositionFactory.fromAssetSync(context, "Tests/DynamicGradient.json").value!! snapshotComposable("Compose Dynamic Gradient") { + val composition = LottieCompositionFactory.fromAssetSync(context, "Tests/DynamicGradient.json").value!! val dynamicProperties = rememberLottieDynamicProperties( rememberLottieDynamicProperty( LottieProperty.GRADIENT_COLOR, @@ -34,5 +38,48 @@ class ComposeDynamicPropertiesTestCase : SnapshotTestCase { ) LottieAnimation(composition, 0f, dynamicProperties = dynamicProperties) } + + val heartComposition = LottieCompositionFactory.fromAssetSync(context, "Tests/Heart.json").value!! + snapshotComposable("Compose Dynamic Image", "Default") { + val composition by rememberLottieComposition(LottieCompositionSpec.Asset("Tests/Heart.json")) + LocalSnapshotReady.current.value = composition != null + LottieAnimation(composition, 0f) + } + snapshotComposable("Compose Dynamic Image", "Default - Maintain Original Bounds") { + LottieAnimation(heartComposition, 0f, maintainOriginalImageBounds = true) + } + snapshotComposable("Compose Dynamic Image", "Smaller") { + val bitmap = getBitmapFromAssets("Images/Heart-80.png") + val dynamicProperties = rememberLottieDynamicProperties( + rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "Heart"), + ) + LottieAnimation(heartComposition, 0f, dynamicProperties = dynamicProperties) + } + snapshotComposable("Compose Dynamic Image", "Smaller - Maintain Original Bounds") { + val bitmap = getBitmapFromAssets("Images/Heart-80.png") + val dynamicProperties = rememberLottieDynamicProperties( + rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "Heart"), + ) + LottieAnimation(heartComposition, 0f, dynamicProperties = dynamicProperties, maintainOriginalImageBounds = true) + } + snapshotComposable("Compose Dynamic Image", "Larger") { + val bitmap = getBitmapFromAssets("Images/Heart-1200.png") + val dynamicProperties = rememberLottieDynamicProperties( + rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "Heart"), + ) + LottieAnimation(heartComposition, 0f, dynamicProperties = dynamicProperties) + } + snapshotComposable("Compose Dynamic Image", "Larger - Maintain Original Bounds") { + val bitmap = getBitmapFromAssets("Images/Heart-1200.png") + val dynamicProperties = rememberLottieDynamicProperties( + rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "Heart"), + ) + LottieAnimation(heartComposition, 0f, dynamicProperties = dynamicProperties, maintainOriginalImageBounds = true) + } + } + + private fun SnapshotTestCaseContext.getBitmapFromAssets(name: String): Bitmap { + @Suppress("BlockingMethodInNonBlockingContext") + return BitmapFactory.decodeStream(context.assets.open(name), null, BitmapFactory.Options())!! } } \ No newline at end of file diff --git a/snapshot-tests/src/main/assets/Images/Heart-1200.png b/snapshot-tests/src/main/assets/Images/Heart-1200.png new file mode 100644 index 0000000000000000000000000000000000000000..27f52bf76456e9740c7ed0121961a829ce8ff4e1 GIT binary patch literal 30430 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 av.maintainOriginalImageBounds = i <= 4 } } fun setFontAssetDelegate(delegate: FontAssetDelegate?) {