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 886747c3a..837cbb866 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 @@ -69,6 +75,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, @@ -106,6 +113,7 @@ fun LottieAnimation( drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers drawable.enableMergePathsForKitKatAndAbove(enableMergePaths) drawable.useSoftwareRendering(useSoftwareRendering) + drawable.maintainOriginalImageBounds = maintainOriginalImageBounds drawable.progress = progress drawable.setBounds(0, 0, composition.bounds.width(), composition.bounds.height()) drawable.draw(canvas.nativeCanvas, matrix) @@ -133,6 +141,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, @@ -153,6 +162,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 e050a5a68..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,15 +19,14 @@ import com.airbnb.lottie.value.ScaleXY * This takes a vararg of individual dynamic properties which should be created with [rememberLottieDynamicProperty]. * * @see rememberLottieDynamicProperty - * @see LottieDrawable.setRescaleBitmaps + * @see LottieDrawable.setMaintainOriginalImageBounds */ @Composable fun rememberLottieDynamicProperties( - rescaleBitmaps: Boolean = false, vararg properties: LottieDynamicProperty<*>, ): LottieDynamicProperties { - return remember(rescaleBitmaps, properties) { - LottieDynamicProperties(rescaleBitmaps, properties.toList()) + return remember(properties) { + LottieDynamicProperties(properties.toList()) } } @@ -93,7 +92,6 @@ class LottieDynamicProperty internal constructor( * @see rememberLottieDynamicProperties */ class LottieDynamicProperties internal constructor( - private val rescaleBitmaps: Boolean, private val intProperties: List>, private val pointFProperties: List>, private val floatProperties: List>, @@ -105,8 +103,7 @@ class LottieDynamicProperties internal constructor( private val bitmapProperties: List>, ) { @Suppress("UNCHECKED_CAST") - constructor(rescaleBitmaps: Boolean, properties: List>) : this( - rescaleBitmaps, + constructor(properties: List>) : this( properties.filter { it.property is Int } as List>, properties.filter { it.property is PointF } as List>, properties.filter { it.property is Float } as List>, @@ -118,7 +115,6 @@ class LottieDynamicProperties internal constructor( ) internal fun addTo(drawable: LottieDrawable) { - drawable.rescaleBitmaps = rescaleBitmaps intProperties.forEach { p -> drawable.addValueCallback(p.keyPath, p.property, p.callback.toValueCallback()) } diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java index 538ac06a2..e4e3556b4 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java @@ -900,7 +900,7 @@ public String getImageAssetsFolder() { * Defaults to false. */ public void setRescaleBitmaps(boolean rescaleBitmaps) { - lottieDrawable.setRescaleBitmaps(rescaleBitmaps); + lottieDrawable.setMaintainOriginalImageBounds(rescaleBitmaps); } /** @@ -910,7 +910,7 @@ public void setRescaleBitmaps(boolean rescaleBitmaps) { * Defaults to false. */ public boolean getRescaleBitmaps() { - return lottieDrawable.getRescaleBitmaps(); + return lottieDrawable.getMaintainOriginalImageBounds(); } /** diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java index eef6f23ec..1521163e0 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java @@ -99,7 +99,7 @@ public void onAnimationUpdate(ValueAnimator animation) { @Nullable TextDelegate textDelegate; private boolean enableMergePaths; - private boolean rescaleBitmaps = true; + private boolean maintainOriginalImageBounds = false; @Nullable private CompositionLayer compositionLayer; private int alpha = 255; @@ -221,23 +221,23 @@ public String getImageAssetsFolder() { } /** - * When true, dynamically set bitmaps will be drawn at the size of the original bitmap. - * When false, dynamically set bitmaps will be drawn at 0,0 at the original bitmap but at whatever size the dynamic bitmap is. + * 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 true. + * Defaults to false. */ - public void setRescaleBitmaps(boolean rescaleBitmaps) { - this.rescaleBitmaps = rescaleBitmaps; + public void setMaintainOriginalImageBounds(boolean maintainOriginalImageBounds) { + this.maintainOriginalImageBounds = maintainOriginalImageBounds; } /** - * When true, dynamically set bitmaps will be drawn at the size of the original bitmap. - * When false, dynamically set bitmaps will be drawn at 0,0 at the original bitmap but at whatever size the dynamic bitmap is. + * 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 true. + * Defaults to false. */ - public boolean getRescaleBitmaps() { - return rescaleBitmaps; + public boolean getMaintainOriginalImageBounds() { + return maintainOriginalImageBounds; } /** 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 e68ebf0b3..11ade548e 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 @@ -48,7 +48,7 @@ public class ImageLayer extends BaseLayer { canvas.save(); canvas.concat(parentMatrix); src.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); - if (lottieDrawable.getRescaleBitmaps()) { + 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)); 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..e11af6d00 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,10 +1,13 @@ 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 com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieDynamicProperty import com.airbnb.lottie.compose.rememberLottieDynamicProperties import com.airbnb.lottie.compose.rememberLottieDynamicProperty import com.airbnb.lottie.snapshots.SnapshotTestCase @@ -14,8 +17,8 @@ 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 +37,46 @@ class ComposeDynamicPropertiesTestCase : SnapshotTestCase { ) LottieAnimation(composition, 0f, dynamicProperties = dynamicProperties) } + + val heartComposition = LottieCompositionFactory.fromAssetSync(context, "Tests/Heart.json").value!! + snapshotComposable("Compose Dynamic Image", "Default") { + LottieAnimation(heartComposition, 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 000000000..27f52bf76 Binary files /dev/null and b/snapshot-tests/src/main/assets/Images/Heart-1200.png differ diff --git a/snapshot-tests/src/main/assets/Images/Heart-80.png b/snapshot-tests/src/main/assets/Images/Heart-80.png new file mode 100644 index 000000000..8f3eae4ab Binary files /dev/null and b/snapshot-tests/src/main/assets/Images/Heart-80.png differ diff --git a/snapshot-tests/src/main/assets/Tests/Heart.json b/snapshot-tests/src/main/assets/Tests/Heart.json new file mode 100644 index 000000000..43edfe5b6 --- /dev/null +++ b/snapshot-tests/src/main/assets/Tests/Heart.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":0,"op":32400,"w":400,"h":400,"nm":"Comp 1","ddd":0,"assets":[{"id":"image_0","w":400,"h":400,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAYAAACAvzbMAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAgAElEQVR4nO3debwkZX3v8e+vurq6zzAsyk5EweUGJUZWZREdXNhlSdwlioZo9Eo0gsYkXnNvNsONcb9qjEZUDJEQBVQMiygRBBRQCEhMQBCVxQEFZuZ0VXVV/e4ffUa2Yeacma56qrs/79eLf2Cmnu8wp59vP/XUIgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgCZY6ABYP5eWa9myLVQU26jT2UJVtbmiaDPleU9R1JcUa/T36Op0Cg2HhaRccbxaZqtUlmvU6dytNL1H0mqTqqB/IEw1lyJJy9Xvb62y3EadzmZy31xFsVxSom43Vlk+8DMrFaqqgZIkV1WtURStUlnerzi+W/Pz95u0OugfCOtFgbSEj/4uttXc3BMl7ayq2kHS4+W+o8y2lPs2MttC0uYyWy73rkbl0dEDH8ZKUimpkLRGow/fakl3y+weud8j99sk3aYoul1mN2swuMtGvx5YEpdizc1tL/cnqap2kvR4mT1eZlvLfWtJ20havvDPZnrg5zXSAz+zpaShzAq5r5a0Su73y+xuud8nszs0+nm9Q9JPNBjcImmljX4vAqNAAnBpuebmdpf7birLvWT2RJk9VdJOkuYajJJLukPSDZJuVVVdJekGDYc/4JsfHsyl5ep2nyZpd0XRvpKeIGl3STtKShqMMi/pDrnfKPcfqdO5Rmb/qcHgeht9aUKDKJAG+Gab7aCieKbM9lNZ7iOzvWS2dehcj8r9HpldJferVZaXqSy/Y9LdoWOhOS49Vr3efnJ/pqT9Je3d+p9Z92sURddK+pbi+Du2Zs2doWNNOwqkJh7HBymKDlYUrZD7fmp2ZTFuqyVdrij6uoriQhsOrwkdCOPny5btJfcVKssXyOxASVuEzrQJUpl9R9Ilki5Uml7G/t/4USBj4lKkXu9QSS+S9AJJTwkcqU7Xyv08VdVXrCi+HToMNp7H8QGKoqNkdqikvULnqdEtkr4m6cvKsovY9xsPCmQTeb//HJm9WFV1pKQnhs4TwLUy+6Kq6ouW59eHDoMN8yR5uqLoOLkfI2kvmUk+U3vSP1YUnavh8Cwrin8PHWaSUSAbwfv9XVUUL1Gn80pJzwidpzWq6iJJn9NweIZJw9Bx8ACXYiXJS2T2WkkvDJ2nRa6V+xdkdqZl2c2hw0waCmQJfG7uQJXliTI7XqNLErFut8rss5JOszS9JXSYWeb9/q6SXiP3V0vaNXSeFivl/nl1Op+wweCy0GEmBQWyCN7pvFhxfJKk54TOMmGGkk5TVX3UhsPvhw4zS7zb3UNR9EZJr5XUDZ1nwlyqovigleVZoYO0HQWyHt7vHy/prXLfO3SWibX2/Lr7mYrj99r8/HdDR5pmvmzZvqqqt8j9VTO4tzFu35PZByxNPxs6SFtRIOvgSfJSmb1T7nvK+F80NmZSVZ0h6W8sz68LHWeaeLf7DJmdLLPfCZ1lCn1f7qdanv9z6CBtw+z4IN7vv0Du75Z0UOgsU+uBb8UfVxT9lQ0GPw0daZL53NzjVFV/Kun3WXHU7lKZ/R9L04tCB2kLCkSS93pPUlX9uaLolaGzzBSz1aqq9yjPT7XRM5GwSC5FiuN3qNP5E0mbh84zU8y+IPd3WZbdFDpKaDNfIJ4kf6Qo+nNJCd/egvkvub/T8vxLoYNMAu/1jpZ0qsx242c2ELOhqup/W57/degoIc1sgXgcr1Cn8wGZPYMPYUuY/bPM3s5prXVzaUf1+++X+8tCZ8EC92sVRX9oafqN0FFCmLkCcSnS3Nx7VVV/GDoL1uk+VdUpNhx+MnSQNvFu93XqdN4r98eEzoJ1+oCy7ORZe97WTBWId7v7KYo+qdFjqNFWZlJRnKMkeZPNz98eOk5IvmzZjsrzjyuOj2al3HrXqyxfb0VxeeggTYlCB2iKJ8k7FEWXy4zyaDt3qdM5RmV5g8/NvTx0nFB8bu4Vqqrr1elQHpPhNxTH3/Y4fnvoIE2Z+hWIS1uq1/u0pONCZ8FG+6Sy7A2zcnpg4cnOfy/pxNBZsJHcz1aen2DSfaGj1GmqC8S73Weq0/kXuT8+dBZsgtENiN+XdLzl+Q2h49TJk2R3SacrivZg1THhzH6isnypDYdXhI5Sl6k9heX9/gkyu5LymALuktkeMvuez829InScuvjc3Cs1enwG5TEN3HdWFF3u/f5rQkepy1QWiCfJX0r6NI8hmTpduf+TJ8l7QgcZN0+Sv5H752XGgw+nz2meJFN5v8jUzbDe758u91eFzoGaRdG5GgxealIWOsqmcKmrXu8sSUeHzoKamX3e0vT40DHGaWoKxKWu+v2vyf35obOgIe7XqdM5clJvPPS5uZ1VVV8WLyWbHWZfV5oeYVIeOso4TEWBuLSF+v2Leez6DDK7R2V5mA2HV4WOshTe7e4ts/NltnXoLGiY2dVK0+eZdH/oKJtq4vdAXHqser0rJFEes8h9a0XR5R7Hh4SOslgex4eo07mc8phZe6vbvdKlbUIH2VQTXSAubaNe70qZPZWrVmZarDg+fxJuOvQkebk6nfPlzmb5rBrdKLub+v0rJr1EJrZAFm4QvExmT6Y8IHepqs7wbvd1oaM8Gu92XyezM0LnQAuM5qwnKUm+7dJWoeNsrIncA3EpUa93jcx2pzywDm+2LPt/oUM8mPd6b5LUqkxogdFNsjcoz/c0aRg6zlJN5gokSS6hPLAeH/Fe742hQ6y1kIXywCO5S1G0u/r9S0JH2RgTVyA+N3e2zPajPLABH/Vu9/dCh/Bu90RJHw2dAy3mLrnv73Nz54SOslQTVSDe7X5E7seEzoEJEUWf8H4/2E2lPjf3SkXRP4QaHxPG/Wjvdj8cOsZSTMweiPd6JymKPqRqJh7IinExk4bDI60sz2tyWO90Dle3ex4rZSyJmRTHJ9nq1R8JHWUxJqJAPI4PVqdzcegcmGBVtZ8Nh1c2MZR3u89SFE3tE1hRs9EL1Q62ovhm6Cgb0voCcWl7zc39SFW1LHQWTDCzeUm7W5reWucw3u/vIukHcp+rcxxMObN5pekTTbordJT1af8eSK93HuWBTea+TNI3XartZ8mlZXL/BuWBTea+TEny1dAxNqTVBeLd7gdltlfoHJgS7k/Q3NwFtR1/bu4iSbvUdnzMFrO9fbPNPhA6xvq0tkA8jg9Tp/MHbEJirNwP9CT51NgPmySfkvv+4z4uZlxRvMXj+NDQMR5NK/dAfJttNtfq1T+T++ahs2AKmUnub7Us++A4Due93ltl9n6+7KAWZvdryy13srvuWhM6ysO1s0CS5ByZ8YId1Gd0pctBVhSXbsphPI6frTj+FuWBWrmfa3neunvgWlcgniQvltm/hM6BGdDp3Kf5+V1N+uXG/HaXHqNly25VWW4x7mjAI7i/1PK8VXNjqwrEt912uVatukPuy0NnwYwwu8zS9Nkb81u9379U7geOOxKwTmZrlKY7mLQ6dJS12rWJfu+9H5dEeaBJB3q//+dL/U3e7/+FJMoDTdpM3e7HQod4sNasQLzb3U+dzuWcS0bjOh0pz1dYUSzqiagex89Rklyisqw7GfBQZlJZNvZUhQ1pT4EkyY2Kot0oEARhdo/SdCeT8vX9MpcSJcntvI4WQYzeH3Kj5fnTQkeRWnIKy/v9EykPBLa1kuT0Df6qJDldUUR5IIzR+0Oe6t3u74aOIrVgBbLwdsGVkriSBeFF0UtsMDhrXf/J5+ZerKpq1VUwmFn3Kcu2Df0Ww/ArkCR5l8woD7RDWX7Gt932ERdy+DbbbK6q+kyISMAjmG2pJHlX8BghB/fHPGZLDQYr5d4NmQP4FXfJ7IuWZb/9kH/d631R7sfJgi/agRGzoZYt28Z+8Yv7Q0UIuwKZn3835YFWMZPMfss7ncPX/ivvdA6XGeWBdnHvas2ad4eMEOwT4VtttZUGg5WS4lAZgPVYaVm2nSR5r/dzmW3LRR5ooeHCXsh9IQYPtwKZn3+7zCgPtNW2Pjf3Zp+be7MkygNt1VW///ZQgwdZgbjUV6+3UmbL+WACwCZZvbAKSZseOMwKpNv9PcoDAMZiubrdE0MMHGYF0u/fIvddQowNAFPH7FZL012bHrbxFcjC27V2aXpcAJhiu3gcH9L0oM2fworjt3DqCgDGyH00tzas0VNYLu2kXu9nTY4JADMjy3Yy6Y6mhmt2BZIkr+ZmLACogdlojm1yyCYH837/P+X+602OCQAzw+w/LU2f2tRwja1AvNvdUxLlAQD12c273Wc0NVhzp7DMXt7YWAAwq8xe0dhQTQ3kvd7Nkp7Y1HgAMKNusix7ShMDNbICWVhSUR4AUL8ne5L8ZhMDNXMKy+zoRsYBAEhSI3NuU3sgh3H5LgA0xOywRoapewCXtlWvd6dCv7wKAGaFWak03dGklXUOU/+kniQrZEZ5AEBzOkqS59Q9SP0Tu9mK2scAADyU+4q6h2hiZXAAD08EgAa5S1F0YN3D1LoH4tJ26vVul9SpcxwAwCMUCw9XrG0fpN4VSKezr8woDwBomlmsXm/fOoeou0D24vQVAATgLrnvVecQde+B7MH9HwAQwGju3bPOIeotELPdWYEAQADuktnT6hyitgJxaXtJT67r+ACADXqyS9vVdfD6ViBx/D/E1VcAEFK8MBfXor4CMWP1AQCh1TgX11cgnc6Tajs2AGBxut3a5uI6N9F3YQMdAAIyk4pi17oOX1+BuO/MJbwAENDoSqzH1XX4Olcg29d4bADA4tQ2F9dSIC4tl7RDHccGACyB2fYLc/LY1bMC6fe3kbRFLccGACye+5bq97eu49D1FEhZbq0G3nYIANigSGX52HoOXIdOZ6tajgsAWLqa5uR6CmQ4XM4VWADQAmbScFjLlkJdV2HVsmEDANgoE7SJ3u0u4yZCAGgBd6nXm6vj0PUUiHu/luMCAJauLCeoQKRuTccFACxdLXNyXQXCY9wBoD1qmZPrKZAo4hIsAGiLmubkegqkqqpajgsAWLqqKus4bF2nsCgQAGiPWuZkCgQAph8FAgDYKBNVIEVNxwUALFVV1TIn11MgZlktxwUALF2nU8ucXNfDFDMepggALWAmVdUEFYiU13RcAMBSlWUtc3I9BdLr5TxMEQBawF3qdCaoQMpyvpbjAgCWzqyWObmuR5lQIADQFjXNyfUUSFFQIADQFjXNyXVdxjuo5bgAgKWbqBVIp7NK3EwIAG0wXJiTx66eAhkM7pe0ppZjAwAWz2y15ucnqEBG5XFfTccGACyWe21f6GspEJNKSffXcWwAwJLcbxP2MEWJFQgAtEFtc3F9BeJ+T23HBgAsTo1zcX0FYraytmMDABanxrm4zgK5u7ZjAwAWZ0ILhBUIAITmPoEFUpa3804QAAjITCqKO+o6fH0FEkV38kh3AAjIXTKbwAKpqjtrOzYAYHGi6K7aDl3XgZXnt4vHmQBASKsX5uJa1FYgNrp55ad1HR8AsEE/tRqfClLnneiS9JOajw8AeHS31XnwegvE/TauxAKAAEZzb61f4utegdzKlVgAEIC75H5LnUPUWyBl+d+sQAAgADOpLG+qc4h6CySKbmIFAgABuI/m4BrVWyDD4U2S0lrHAACsy0DD4c11DlBrgZh0r6Qf1TkGAGCdfrQwB9em7k10yf1G9kEAoEFmo7m3ZvUXSBRdzz4IADRoNOdeX/cw9RdIVd3ACgQAGjSac2+oe5j6C0RiBQIATWpoBVL70sClSL3eTyXtWPdYAABJ0u3Ksp1NquocpPYViEmV3L9X9zgAAK3dQL+m7vKQmjmFJUnXNDQOAMy20emrRr60N1MgcXxVI+MAABqbcxu5PMql7dTr3S6p08R4ADDDCmXZTiatrHugRlYgJv1c7tc1MRYAzLjrmigPqbk9EMnsCu4HAYAajebYy5sarrkCcf/3xsYCgFnV4Fzb2JLApe0X9kGaKy0AmC3Vwv7HXU0M1thkbtJdMuN+EACoi9nVTZWH1PRqoKq+3uh4ADBLquriJodrukDOYyMdAGpgNppjmxyyycFcipUkd8ps6ybHBYAZcLeybAeTyqYGbHQFYlIh6UJWIQAwRqPnX13QZHlIIa6IKstzGx8TAKZdWX656SEbXwq4tLl6vZWSek2PDQBTKlWWbWvS6iYHbXwFYtIqSRfwkikAGAN3KYouaLo8pFA39ZmdxT4IAIzBaP/jX4IMHWJQl5YvnMbqhxgfAKbIYOH01ZqmBw6yAjFptdzPZRUCAJtgtPo4J0R5SCGfS1WWnwk2NgBMi7L8bKihgy0BXDL1endK2i5UBgCYcHcuPDwxyFVJwVYgJrncT+M0FgBshNHpq9NClYcUcAUiSS49Sb3eTSEzAMDEyrInm3RzqOGDvpvDpJtl9q2QGQBgIpldErI8pDa83Mn9vZzGAoAlGJ2++rvgMUIHkCTv9X4maafQOQBgIpj9zNL0caFjhF+BSNJw+CFWIQCwCGZSnn8wdAypLSsQHrAIAIsV5MGJ69KKFYhJqxRFnwidAwBaL4r+vg3lIbVkBSJJPjf3a6qqn4bOAQCt1un8ms3P3x46htSSFYgk2WDwM7mfzl4IAKzD6J3nn2tLeUgtWoFIkku7qN+/hXeFAMDDmElpuotJPw4dZa3WrEAkyaRbVVVfYBUCAA8yWn2c0abykFq2ApFYhQDAI7Rw9SG1bAUiLaxCyvIfWYUAgEblUZafalt5SC1cgUiSS9uq1/t56BwA0ApZtp1JK0PHeLjWrUAkyaSViqJTQ+cAgOCi6NQ2lofU0hWIJLmUKEl+LrMtQ2cBgCDc71Oeb2dSHjrKurRyBSJJJuWKolPYCwEwk8ykKDqlreUhtXgFspYnyY2Kot24KgvAzBhdtnuj5fnTQkdZn9auQH7F/bWhIwBA49xPCB1hQ1pfIDYcXqGy5OZCALNhdNnuP9tw+J3QUTZkImZll7ZSv3+n3HncO4DpZpYpTXcw6d7QUTak9SsQSTLpXg2Hb2IVAmCqmUnD4RsnoTykCVmBrOVJcoXMnhU6BwDUwv0Ky/P9Q8dYrMkqkH7/CXK/NXQOABg7d6nTeYINBreFjrJYE3EKay1L0x8rik4OnQMAxmpUHidPUnlIE7YCWcv7/e/KfZ/QOQBgLMyusjTdN3SMpZqoFcivmP02NxYCmAruozltAk1kgdhgcJvcX89VWQAmmpnk/vpJO3W11kTPwJ4k5ymKDmc1AmDijL4An2dpemToKBtrsgtEWq5e7yeStgqdBQCWxOyXStOdTVoTOsrGmshTWGuZtFpleayiif5jAJg1USQVxXGTXB7ShBeIJFlRXCL3v2Q/BMBEGO17/IUVxSWho2yqqZl1vd//ptyfGzoHAKyX2SWWpitCxxiH6SkQaQv1+7fI/bGhswDAOsXxL7VmzS4m3R86yjhM/CmstUy6X0VxFKeyALRWVR05LeUhTVGBSJIVxeVyPyl0DgB4iNEX25NsMLg8dJRxmsqv654kn1YUncD9IQCCG72e9jOW5yeEjjJuU1kgkuS93tWS9gqdA8DMu8aybO/QIeowvQUiPVbLlv2XynLr0FkAzKgo+oUGg6eY9IvQUeowVXsgD2bSLzQcviB0DgAzrCieP63lIU1xgUiSDYffl/vLuFMdQKNGNwu+zIbD74eOUqepn1ktz8+U9M7QOQDMELN3Lsw9U21q90Aeznu9j8vsDVyZBaA2o5XH31uW/X7oKE2YmQKRJF+27KuqqiMoEQBjZyZF0ddsfv6I0FGaMlMFIkmeJFfLjMt7AYyX+zWW51N5ue6jmb0CkZar3/8Pue8SOguAKWH2Y6Xp001aFTpKk6Z+E/3hTFots+dKui90FgBT4V5Jz5218pBmsECkX71T/SCZFaGzAJhgZoXcn2tp+uPQUUKYyQKRJMvz/1BRHMzTewFsFDOpKFZYnl8XOkooM1sgkmRFcamGw6NC5wAwYcyk4fBFVhSXhY4S0kwXiCRZWX5VUfTy0DkATBCzV1pZfiV0jNBmvkAkyQaDL8jsd0PnADABqupEGwzOCB2jDSiQBZam/yiJl1EBWJ+TbDj8VOgQbUGBPIhl2UckvS10DgCtdMrCHIEFFMjDWJa9X+48fBHAA9z/2LLs70LHaBsKZB0sz0+V+5+EzgGgBdz/1PL8b0LHaCMK5FFYnr9HUfTHoXMACMj9nZbnfx06RltxF90GeK93iqS/DZ0DQMOi6B02GPDZXw8KZBG813ubJM5/ArPjZMuy94UO0XYUyCJ5r/cWSR8InQNA7d5mWfb+0CEmAQWyBN7rvVnSh0PnAFCbP7As4zO+SBTIEnmv9/ty/xgPYQSmzhstyz4eOsQkYRbcCJ4kxyuKPsercYEpMPoy+GpL08+FjjJpKJCN5J3OsYrjL4XOAWATjL4EHmd5fnboKJOIAtkEHscvVBxfwEoEmFBZdqhJF4SOMam4kXATWFFcqKJ4tszy0FkALIFZrrI8iPLYNBTIJrKiuExlub9m8H3IwIRarSg6wIri0tBBJh2nsMbEk+SpMrtY0g6hswB4VHfJ/WDL8xtDB5kGrEDGxPL8RkXRPpJ+GDoLgHX6oaJoH8pjfFiBjJlLmytJLpTZs0JnAaDRZbpl+V0Nh883TjWPFSuQMTNplfL8QJmxOQe0w4UaDg+gPMaPAqmBSaWl6aEy+wJ3rAMBuZ9paXqISUXoKNOIAqmRpenL5f7R0DmAmeMuRdHHLM9fFjrKNKNAamZZ9j/l/lesRICGmElV9Vc2GLwpdJRpx6zWEO/1/kDSB0PnAGbAWyzLPhQ6xCygQBrkSfJSmX0hdA5garm/3PKcz1hDKJCGeRwfrDj+qtznQmcBpoZZqqI40ori4tBRZgkFEoAnydNldoHMduBBjMAmu0vuL7Q8/4/QQWYNBRKISzsu3HC4e+gswAT7gTqdF9r8/O2hg8wiCiQgl5ap3/+a3J8TOgswgS5Rlh1h0nzoILOKy3gDMmne0vS5MjuTy3yBRTKTzM60LFtBeYRFgbSApenLVJbvC50DmAhl+X5LU24QbAEKpCVsODxZ0imhcwAt9w4bDt8WOgRGOG/SMp4kr5DZP4XOAbSO+6ssz/lstAgF0kIexysUx1+R+2ahswDBmc2rKI6yovhG6Ch4KAqkpRbecHi+pJ1DZwGCMfuJquowy/MfhI6CR2IPpKUsz29Ulu2pqvpO6CxAEO5Xqdvdi/JoLwqkxUy6R8Phfoqic7jMFzPDTIqic5Tn+9mqVXeHjoNHR4G0nElug8GxKsuPhM4CNKIsP2KDwbEmlaGjYP0okAlhw+FJcv8jViKYau5/ZMPhSaFjYHGYjSYMl/liarkfb3n++dAxsHgUyATyOD5o4TLfLUJnATaZ2SoVxdFWFN8MHQVLwymsCWRF8S257y33/w6dBdhEN8l9b8pjMrECmWAuba5e71xJK0JnAZasqr6l4fAok+4PHQUbhxXIBDNplWXZwTLjvDEmi9kZNhw+h/KYbBTIFLA0PV7u7+EKLbSemeT+fy1NXxk6CjYdM84U8V7vzTL7MK/JRSuN3uNxkg0G3NM0JSiQKeNJcoykf5VZJ3QW4FfcXWbHWpadGzoKxocCmULe7e6jTuc8SduyGkFwZveoLI+04fDK0FEwXuyBTCEbDq+S2Z6qqutCZ8HMu15me1Ie04kVyBRzKVG/f47cDwudBTPpfGXZcSYNQgdBPViBTDGTckvTwyV9InQWzJxPWpYdRnlMNwpkBliWvUFl+b9C58CMcP8zy7LfCx0D9eMU1gzxfv81cj8tdA5MMbPXWZp+OnQMNIMCmTEex89THJ/L+9YxZvMqy2OsKC4KHQTNoUBmkCfJbjI7T9KuobNgKvxY7kfw6tnZQ4HMKJe2Uq/3FUkHhs6CCeZ+ufL8SJN+GToKmscm+owy6V7LsmfL7EyeoYUlG/3MnGV5fgDlMbsokBlnafoyFcXfUiJYkrJ8n2XZS0LHQFjMGpAkea93ksw+xKNPsF6jp+n+oWXZB0JHQXgUCH7Fk+RYSWfxIEask7tL+m3L8y+FjoJ2oEDwEN7t7qsoOk/SNqGzoEXMfqGyPIJnWuHB2APBQ9hw+F1F0R6SeBAj1rpeZntQHng4ViBYJ5d66vXOlsSDGGfbBcqyY3mmFdaFFQjWyaTMsuxwSf8QOguC+aRl2aGUBx4NBYL1six7vcry3aFzoEFmUlnyQERsEKewsCje758g6dNc5jsDeCAiFokCwaJ5HB+iTudsSXOhs6AW2cIDEc8PHQSTgQLBkniS/Iai6N/k/muhs2CMzG5XVR1uec7Vd1g09kCwJJbn1yuO95T71aGzYEzcr1Ec70F5YKlYgWCjuBRrbu5LqqqjQmfBJoiir2ow+C2T8tBRMHlYgWCjmFTYYPAiRdHH2FifQKO/s4/bYHAU5YGNRYFgk9hg8CZV1btC58ASVdW7LcveGDoGJhunsDAWvG99gnCZLsaEAsHYeBy/UJ3OuZL6obNgnXKV5VFWFBeGDoLpQIFgrDxJflNR9G+SdmRvpCVG7/C4U1V1mA2H14aOg+lBgWDsfLPNttdweL7MnhE6CyS5X6c8P8ykO0JHwXShQFALlxL1+1+W+yGhs8w0swuVpkeblIaOgunDVViohUm5pemhMjstdJaZZfZZS9NDKA/UhQJBrSxNXyv398hY7DZm9DTdUy1NXxM6CqYbn2o0wnu9k2T2ITbWGxBFb7XB4IOhY2D6USBojCfJcTL7YugcU839xZbn/xo6BmYDBYJGeRwfoDj+N7lvHjrLVDFbpaI4wori0tBRMDvYA0GjrCi+Lfd9ZHYr+yJjYnab3PehPNA0PsEIwqXHqts9X1G0T+gsE839avV6h9qqVfeEjoLZQ4EgGJfihXtFDgudZSKZna80fZFJw9BRMJs4hX+/5oAAAAPiSURBVIVgTCosTQ+X2Wc4nbUEZmvv8TiM8kBIFAiCszQ9QUVxKiWyCGZSUfwt93igDSgQtIIVxTtl9rbQOVrP7GQrineEjgFI7IGgZTxJXiGzfwqdo5Xcj7c8/3zoGMBaFAhax+P4BYrjr8i9FzpLK5jlKoojrSguCh0FeDBOYaF1rCguUlk+S9LPQ2dpgZWKov0oD7QRBYJWsuHwWkXR3pJ+GDpLQP+lKNrb5ue/FzoIsC6cwkKrubS5kuR8me0fOktjzKSqulJ5fohJ94eOAzwaViBoNZNWKc8PlHRe6CyNcf+a8vwAygNtR4Gg9Uxyy7Ij5f7Zqb5XZPTu8s9Zlh1hUhU6DrAhFAgmhuX5a1QUfzeVJTK6QfB9luevDh0FWCwKBBPFiuIUmf1x6Bw1+FMripNDhwCWYgq/ymEWeL9/oqR/mPg3HI5eP/sGGw4/EToKsFQUCCaWJ8kxks6e2FNao/I7zvL87NBRgI0xoZ88YMTj+NmK4/Plvix0liUxG6goDreiuCR0FGBjsQeCiWZFcamqatLuWl+pTudZlAcmHSsQTAWfm3ucqupimT2l1fsiUXSzpOfZYHBb6CjApqJAMDVc2lJJcrHM9gqdZZ3K8nsqiueb9MvQUYBx4BQWpoZJ9ynP95PZN0JneQSzb6oo9qc8ME0oEEwVk4aWps+TdE6Lrs4619L0YJOy0EGAcaJAMJUsy45VVf1j2BAmmX3asuyYoDmAmlAgmFqW57+rsgzz6JPRo0neb2n6uuYHB5pBgWCqWVGcoqr6X40PXFV/ZkXBO94x1Vpzkhiok/d6b5b04YaGe4tl2YcaGgsIhgLBzPAkeZXMTl94bPr4BxidKvsdS9PTx39woH0oEMwU73QOVxzX83KqojjSynJ2XnyFmUeBYOZ4HB+kOL5I7slYDmg2VFG8wIri38dyPGBCsImOmWNF8S1F0QGSVo3hcKsVRQdSHphFrEAws7zX+3W5f1NmO2zkIX4u9xWW5zeONRgwIViBYGZZlv1Qnc4zZfajpf1Gk8xuVRTtS3lglrECwcxzaWvF8TcUx09f1NVZVXWD+v0VtmrV3fWnAwC0mkvLPEm+7b2er/effv9KlzYLnRcA0CIuRd7rfX09BXKxS3HonACAlvJe75x1lMe5oXMBACaA9/uf9X5/7Wkr7iwHACyex/EXfW7u7NA5AAATxqXHe7//hNA5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMNv+P+6G1dr60B8IAAAAAElFTkSuQmCC","e":1}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"Heart","refId":"image_0","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":[200,200,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":32400,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":1,"nm":"Blue Solid 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":[600,600,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"sw":1200,"sh":1200,"sc":"#0600ff","ip":0,"op":32400,"st":0,"bm":0}],"markers":[]} \ No newline at end of file