diff --git a/animated-base/src/main/java/com/facebook/fresco/animation/bitmap/cache/FrescoFpsCache.kt b/animated-base/src/main/java/com/facebook/fresco/animation/bitmap/cache/FrescoFpsCache.kt deleted file mode 100644 index aaafac609b..0000000000 --- a/animated-base/src/main/java/com/facebook/fresco/animation/bitmap/cache/FrescoFpsCache.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.fresco.animation.bitmap.cache - -import android.graphics.Bitmap -import com.facebook.common.references.CloseableReference -import com.facebook.fresco.animation.bitmap.BitmapAnimationBackend.FrameType -import com.facebook.fresco.animation.bitmap.BitmapFrameCache -import com.facebook.fresco.animation.bitmap.BitmapFrameCache.FrameCacheListener -import com.facebook.fresco.animation.bitmap.preparation.loadframe.FpsCompressorInfo -import com.facebook.imagepipeline.animated.base.AnimatedImage -import com.facebook.imagepipeline.animated.base.AnimatedImageResult -import com.facebook.imagepipeline.cache.AnimatedCache -import com.facebook.imagepipeline.cache.AnimationFrames -import java.util.concurrent.TimeUnit - -/** Bitmap frame cache used for animated drawables */ -class FrescoFpsCache( - private val animatedImageResult: AnimatedImageResult, - private val fpsCompressorInfo: FpsCompressorInfo, - private val animatedDrawableCache: AnimatedCache -) : BitmapFrameCache { - - /** Unique reference for this animation asset */ - private val cacheKey: String = - animatedImageResult.source ?: animatedImageResult.image.hashCode().toString() - - /** Reference to the loaded animation */ - private var animationFrames: CloseableReference? = - animatedDrawableCache.findAnimation(cacheKey) - - override fun getCachedFrame(frameNumber: Int): CloseableReference? { - return safeAnimationFrames()?.getFrame(frameNumber) - } - - override fun getFallbackFrame(frameNumber: Int): CloseableReference? { - return null - } - - override fun getBitmapToReuseForFrame( - frameNumber: Int, - width: Int, - height: Int - ): CloseableReference? { - return null - } - - override fun contains(frameNumber: Int): Boolean { - return getCachedFrame(frameNumber) != null - } - - override val sizeInBytes: Int - get() = safeAnimationFrames()?.sizeBytes ?: 0 - - override fun clear() { - releaseCache() - } - - private fun releaseCache() { - animatedDrawableCache.removeAnimation(cacheKey) - animationFrames = null - } - - override fun isAnimationReady(): Boolean = safeAnimationFrames()?.frames.orEmpty().size > 1 - - override fun onFrameRendered( - frameNumber: Int, - bitmapReference: CloseableReference, - @FrameType frameType: Int - ) = Unit - - override fun onFramePrepared( - frameNumber: Int, - bitmapReference: CloseableReference, - @FrameType frameType: Int - ) = Unit - - override fun onAnimationPrepared(frameBitmaps: Map>): Boolean { - val loadedFramesCount = safeAnimationFrames()?.frames.orEmpty().size - - // If saved quality is higher than new, then skip - if (frameBitmaps.size < loadedFramesCount) { - return true - } - - animationFrames = compressAnimation(frameBitmaps) - return animationFrames != null - } - - private fun compressAnimation( - frameBitmaps: Map> - ): CloseableReference? { - var fps = animatedImageResult.image.fps() - var animationFrames: CloseableReference? = null - - while (animationFrames == null && fps > 1) { - val compressionResult = - fpsCompressorInfo.compress(animatedImageResult.image.duration, frameBitmaps, fps) - val animation = - AnimationFrames(compressionResult.compressedAnim, compressionResult.realToReducedIndex) - animationFrames = animatedDrawableCache.saveAnimation(cacheKey, animation) - - if (animationFrames != null) { - compressionResult.removedFrames.forEach { it.close() } - } - - fps -= FPS_COMPRESSION_STEP - } - - return animationFrames - } - - /** - * Return animation frames based on the cacheKey. Check if current animationFrames exists, - * otherwise fetch into AnimationCache in case the animation was loaded previously and they are - * valid - * - * @return animation frames which contains the bitmaps of the animation - */ - @Synchronized - private fun safeAnimationFrames(): AnimationFrames? { - val animatedCache = - animationFrames ?: animatedDrawableCache.findAnimation(cacheKey) ?: return null - - // animatedCache instance is shared between this class and AnimatedCache class. Then we need to - // specify that this instance cannot be modified when we perform .get() - return synchronized(animatedCache) { if (animatedCache.isValid) animatedCache.get() else null } - } - - override fun setFrameCacheListener(frameCacheListener: FrameCacheListener?) = Unit - - private fun AnimatedImage.fps(): Int { - val frameMs = duration.div(frameCount.coerceAtLeast(1)) - return TimeUnit.SECONDS.toMillis(1).div(frameMs.coerceAtLeast(1)).toInt() - } - - companion object { - private const val FPS_COMPRESSION_STEP = 1 - } -} diff --git a/animated-base/src/main/java/com/facebook/fresco/animation/factory/AnimatedFactoryV2Impl.java b/animated-base/src/main/java/com/facebook/fresco/animation/factory/AnimatedFactoryV2Impl.java index 7df66d9355..77f54296e0 100644 --- a/animated-base/src/main/java/com/facebook/fresco/animation/factory/AnimatedFactoryV2Impl.java +++ b/animated-base/src/main/java/com/facebook/fresco/animation/factory/AnimatedFactoryV2Impl.java @@ -27,7 +27,6 @@ import com.facebook.imagepipeline.animated.impl.AnimatedDrawableBackendProvider; import com.facebook.imagepipeline.animated.util.AnimatedDrawableUtil; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; -import com.facebook.imagepipeline.cache.AnimatedCache; import com.facebook.imagepipeline.cache.CountingMemoryCache; import com.facebook.imagepipeline.common.ImageDecodeOptions; import com.facebook.imagepipeline.core.ExecutorSupplier; @@ -47,8 +46,6 @@ public class AnimatedFactoryV2Impl implements AnimatedFactory { private static final int NUMBER_OF_FRAMES_TO_PREPARE = 3; - private static final int BALANCED_STRATEGY_PREPARATION_MS = 10000; // No CPU - private final PlatformBitmapFactory mPlatformBitmapFactory; private final ExecutorSupplier mExecutorSupplier; private final CountingMemoryCache mBackingCache; @@ -60,30 +57,24 @@ public class AnimatedFactoryV2Impl implements AnimatedFactory { private @Nullable DrawableFactory mAnimatedDrawableFactory; private @Nullable SerialExecutorService mSerialExecutorService; private int mAnimationFpsLimit; - private final AnimatedCache mAnimatedCache; - private final boolean mUseBalancedAnimationStrategy; - private final int mBalancedStrategyPreparationMs; + private final boolean mUseBufferLoaderStrategy; @DoNotStrip public AnimatedFactoryV2Impl( PlatformBitmapFactory platformBitmapFactory, ExecutorSupplier executorSupplier, CountingMemoryCache backingCache, - AnimatedCache animatedCache, boolean downscaleFrameToDrawableDimensions, - boolean useBalancedAnimationStrategy, - int balancedStrategyPreparationMs, + boolean useBufferLoaderStrategy, int animationFpsLimit, SerialExecutorService serialExecutorServiceForFramePreparing) { mPlatformBitmapFactory = platformBitmapFactory; mExecutorSupplier = executorSupplier; mBackingCache = backingCache; - mAnimatedCache = animatedCache; mAnimationFpsLimit = animationFpsLimit; - mUseBalancedAnimationStrategy = useBalancedAnimationStrategy; + mUseBufferLoaderStrategy = useBufferLoaderStrategy; mDownscaleFrameToDrawableDimensions = downscaleFrameToDrawableDimensions; mSerialExecutorService = serialExecutorServiceForFramePreparing; - mBalancedStrategyPreparationMs = balancedStrategyPreparationMs; } @Nullable @@ -128,7 +119,6 @@ private DefaultBitmapAnimationDrawableFactory createDrawableFactory() { Supplier numberOfFramesToPrepareSupplier = () -> NUMBER_OF_FRAMES_TO_PREPARE; final Supplier useDeepEquals = Suppliers.BOOLEAN_FALSE; - final Supplier animatedCacheSupplier = () -> mAnimatedCache; return new DefaultBitmapAnimationDrawableFactory( getAnimatedDrawableBackendProvider(), @@ -137,14 +127,12 @@ private DefaultBitmapAnimationDrawableFactory createDrawableFactory() { RealtimeSinceBootClock.get(), mPlatformBitmapFactory, mBackingCache, - animatedCacheSupplier, cachingStrategySupplier, numberOfFramesToPrepareSupplier, useDeepEquals, - Suppliers.of(mUseBalancedAnimationStrategy), + Suppliers.of(mUseBufferLoaderStrategy), Suppliers.of(mDownscaleFrameToDrawableDimensions), - Suppliers.of(mAnimationFpsLimit), - Suppliers.of(mBalancedStrategyPreparationMs)); + Suppliers.of(mAnimationFpsLimit)); } private AnimatedDrawableUtil getAnimatedDrawableUtil() { @@ -193,8 +181,6 @@ public AnimatedDrawableBackend get( } }; return new AnimatedImageFactoryImpl( - animatedDrawableBackendProvider, - mPlatformBitmapFactory, - this.mUseBalancedAnimationStrategy); + animatedDrawableBackendProvider, mPlatformBitmapFactory, mUseBufferLoaderStrategy); } } diff --git a/animated-base/src/main/java/com/facebook/fresco/animation/factory/DefaultBitmapAnimationDrawableFactory.java b/animated-base/src/main/java/com/facebook/fresco/animation/factory/DefaultBitmapAnimationDrawableFactory.java index 0e4de1d077..09074ed082 100644 --- a/animated-base/src/main/java/com/facebook/fresco/animation/factory/DefaultBitmapAnimationDrawableFactory.java +++ b/animated-base/src/main/java/com/facebook/fresco/animation/factory/DefaultBitmapAnimationDrawableFactory.java @@ -23,18 +23,14 @@ import com.facebook.fresco.animation.bitmap.BitmapFrameCache; import com.facebook.fresco.animation.bitmap.BitmapFrameRenderer; import com.facebook.fresco.animation.bitmap.cache.AnimationFrameCacheKey; -import com.facebook.fresco.animation.bitmap.cache.FrescoFpsCache; import com.facebook.fresco.animation.bitmap.cache.FrescoFrameCache; import com.facebook.fresco.animation.bitmap.cache.KeepLastFrameCache; import com.facebook.fresco.animation.bitmap.cache.NoOpCache; -import com.facebook.fresco.animation.bitmap.preparation.BalancedAnimationStrategy; import com.facebook.fresco.animation.bitmap.preparation.BitmapFramePreparationStrategy; import com.facebook.fresco.animation.bitmap.preparation.BitmapFramePreparer; import com.facebook.fresco.animation.bitmap.preparation.DefaultBitmapFramePreparer; import com.facebook.fresco.animation.bitmap.preparation.FixedNumberBitmapFramePreparationStrategy; import com.facebook.fresco.animation.bitmap.preparation.FrameLoaderStrategy; -import com.facebook.fresco.animation.bitmap.preparation.loadframe.FpsCompressorInfo; -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFrameTaskFactory; import com.facebook.fresco.animation.bitmap.preparation.ondemandanimation.FrameLoaderFactory; import com.facebook.fresco.animation.bitmap.wrapper.AnimatedDrawableBackendAnimationInformation; import com.facebook.fresco.animation.bitmap.wrapper.AnimatedDrawableBackendFrameRenderer; @@ -49,7 +45,6 @@ import com.facebook.imagepipeline.animated.impl.AnimatedDrawableBackendProvider; import com.facebook.imagepipeline.animated.impl.AnimatedFrameCache; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; -import com.facebook.imagepipeline.cache.AnimatedCache; import com.facebook.imagepipeline.cache.CountingMemoryCache; import com.facebook.imagepipeline.drawable.DrawableFactory; import com.facebook.imagepipeline.image.CloseableAnimatedImage; @@ -81,8 +76,6 @@ public class DefaultBitmapAnimationDrawableFactory private final Supplier mUseDeepEqualsForCacheKey; private final Supplier mUseNewBitmapRender; private final Supplier mDownscaleFrameToDrawableDimensions; - private final Supplier mAnimatedDrawableCache; - private final Supplier mBalancedStrategyPreparationMs; private final Supplier mAnimationFpsLimit; // Change the value to true to use KAnimatedDrawable2.kt @@ -95,14 +88,12 @@ public DefaultBitmapAnimationDrawableFactory( MonotonicClock monotonicClock, PlatformBitmapFactory platformBitmapFactory, CountingMemoryCache backingCache, - Supplier animatedDrawableCache, Supplier cachingStrategySupplier, Supplier numberOfFramesToPrepareSupplier, Supplier useDeepEqualsForCacheKey, Supplier useNewBitmapRender, Supplier downscaleFrameToDrawableDimensions, - Supplier animationFpsLimit, - Supplier balancedStrategyPreparationMs) { + Supplier animationFpsLimit) { mAnimatedDrawableBackendProvider = animatedDrawableBackendProvider; mScheduledExecutorServiceForUiThread = scheduledExecutorServiceForUiThread; mExecutorServiceForFramePreparing = executorServiceForFramePreparing; @@ -113,9 +104,7 @@ public DefaultBitmapAnimationDrawableFactory( mNumberOfFramesToPrepareSupplier = numberOfFramesToPrepareSupplier; mUseDeepEqualsForCacheKey = useDeepEqualsForCacheKey; mUseNewBitmapRender = useNewBitmapRender; - mAnimatedDrawableCache = animatedDrawableCache; mAnimationFpsLimit = animationFpsLimit; - mBalancedStrategyPreparationMs = balancedStrategyPreparationMs; mDownscaleFrameToDrawableDimensions = downscaleFrameToDrawableDimensions; } @@ -186,23 +175,13 @@ private AnimationBackend createAnimationBackend( } if (mUseNewBitmapRender.get()) { - if (mBalancedStrategyPreparationMs.get() != 0) { - bitmapFramePreparationStrategy = - new BalancedAnimationStrategy( - animationInfo, - mBalancedStrategyPreparationMs.get(), - new LoadFrameTaskFactory(mPlatformBitmapFactory, bitmapFrameRenderer), - bitmapFrameCache, - mDownscaleFrameToDrawableDimensions.get()); - } else { - bitmapFramePreparationStrategy = - new FrameLoaderStrategy( - animatedImageResult.getSource(), - animationInfo, - bitmapFrameRenderer, - new FrameLoaderFactory(mPlatformBitmapFactory, mAnimationFpsLimit.get()), - mDownscaleFrameToDrawableDimensions.get()); - } + bitmapFramePreparationStrategy = + new FrameLoaderStrategy( + animatedImageResult.getSource(), + animationInfo, + bitmapFrameRenderer, + new FrameLoaderFactory(mPlatformBitmapFactory, mAnimationFpsLimit.get()), + mDownscaleFrameToDrawableDimensions.get()); } BitmapAnimationBackend bitmapAnimationBackend = @@ -237,13 +216,6 @@ private AnimatedDrawableBackend createAnimatedDrawableBackend( } private BitmapFrameCache createBitmapFrameCache(AnimatedImageResult animatedImageResult) { - if (mUseNewBitmapRender.get()) { - return new FrescoFpsCache( - animatedImageResult, - new FpsCompressorInfo(mAnimationFpsLimit.get()), - mAnimatedDrawableCache.get()); - } - switch (mCachingStrategySupplier.get()) { case CACHING_STRATEGY_FRESCO_CACHE: return new FrescoFrameCache(createAnimatedFrameCache(animatedImageResult), true); diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/BalancedAnimationStrategy.kt b/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/BalancedAnimationStrategy.kt deleted file mode 100644 index e3b8baf6af..0000000000 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/BalancedAnimationStrategy.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.fresco.animation.bitmap.preparation - -import android.graphics.Bitmap -import android.os.SystemClock -import androidx.annotation.UiThread -import com.facebook.common.references.CloseableReference -import com.facebook.fresco.animation.backend.AnimationInformation -import com.facebook.fresco.animation.bitmap.BitmapFrameCache -import com.facebook.fresco.animation.bitmap.preparation.loadframe.AnimationLoaderExecutor -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFrameOutput -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFrameTask -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFrameTaskFactory -import java.io.Closeable -import java.util.SortedSet -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.math.ceil - -/** - * Balanced strategy consist on retrieving the animation frames balancing between RAM and CPU. - * - RAM: Bitmap cache allocates the bitmaps in memory - * - CPU: OnDemand threads load in advance the next missing bitmap in cache - * - * This strategy will increase the CPU usage in order to reduce the RAM allocation. Depending on the - * asset FPS, the RAM could decrease 15-30%. [ON_DEMAND_PREPARATION_TIME_MS] indicates how many ms - * needs the OnDemand thread pool to load the next frame: - * - greater onDemant preparation => lower CPU usage + greater RAM usage - * - * Due to this strategy will be unique per animation, it is highly recommended - * [ON_DEMAND_PREPARATION_TIME_MS] >= 200ms - */ -class BalancedAnimationStrategy( - animationInformation: AnimationInformation, - onDemandPreparationMs: Int, - private val loadFrameTaskFactory: LoadFrameTaskFactory, - private val bitmapCache: BitmapFrameCache, - private val downscaleFrameToDrawableDimensions: Boolean, -) : BitmapFramePreparationStrategy { - - private val fetchingFrames = AtomicBoolean(false) - private val fetchingOnDemand = AtomicBoolean(false) - private val framesCached: Boolean - get() = bitmapCache.isAnimationReady() - - private val onDemandFrames: SortedSet = sortedSetOf() - private var nextPrepareFrames = SystemClock.uptimeMillis() - - private val frameCount: Int = animationInformation.frameCount - private val animationWidth: Int = animationInformation.width() - private val animationHeight: Int = animationInformation.height() - private val onDemandRatio: Int - private var onDemandBitmap: OnDemandFrame? = null - - init { - val avgFrameDurationMs = animationInformation.loopDurationMs.div(frameCount) - val optimisticRatio = ceil(onDemandPreparationMs.div(avgFrameDurationMs.toFloat())).toInt() - onDemandRatio = optimisticRatio.coerceAtLeast(2) - } - - private fun isFirstFrameReady() = bitmapCache.getCachedFrame(0)?.isValid == true - - @UiThread - override fun prepareFrames( - canvasWidth: Int, - canvasHeight: Int, - onAnimationLoaded: (() -> Unit)? - ) { - // Validate inputs - if (canvasWidth <= 0 || canvasHeight <= 0 || animationWidth <= 0 || animationHeight <= 0) { - return - } - - // Validate status - if (framesCached || fetchingFrames.get() || SystemClock.uptimeMillis() < nextPrepareFrames) { - if (framesCached) { - onAnimationLoaded?.invoke() - } - return - } - - fetchingFrames.set(true) - val frameSize = calculateFrameSize(canvasWidth, canvasHeight) - - val task = - if (!isFirstFrameReady()) { - loadFrameTaskFactory.createFirstFrameTask( - frameSize.width, - frameSize.height, - object : LoadFrameOutput { - override fun onSuccess(frames: Map>) { - val cachedSucceed = bitmapCache.onAnimationPrepared(frames) - if (!cachedSucceed) { - nextPrepareFrames = SystemClock.uptimeMillis() + FETCH_FIRST_CACHE_DELAY_MS - } - - // Once first frame is loaded, then load the rest of frames - AnimationLoaderExecutor.execute(loadAllFrames(frameSize, onAnimationLoaded)) - } - - override fun onFail() { - bitmapCache.clear() - fetchingFrames.set(false) - } - }) - } else { - loadAllFrames(frameSize, onAnimationLoaded) - } - - AnimationLoaderExecutor.execute(task) - } - - private fun loadAllFrames(frameSize: Size, notifyOnLoad: (() -> Unit)?): LoadFrameTask { - return loadFrameTaskFactory.createLoadFullAnimationTask( - frameSize.width, - frameSize.height, - frameCount, - object : LoadFrameOutput { - override fun onSuccess(frames: Map>) { - onDemandFrames.clear() - onDemandFrames.addAll(frames.filter { isOnDemandFrame(it.key) }.map { it.key }) - - val memoryFrames = frames.filter { !onDemandFrames.contains(it.key) } - - val cachedSucceed = bitmapCache.onAnimationPrepared(memoryFrames) - if (!cachedSucceed) { - nextPrepareFrames = SystemClock.uptimeMillis() + FETCH_FULL_ANIMATION_CACHE_DELAY_MS - } - - notifyOnLoad?.invoke() - fetchingFrames.set(false) - } - - override fun onFail() { - bitmapCache.clear() - fetchingFrames.set(false) - } - }) - } - - @UiThread - override fun getBitmapFrame( - frameNumber: Int, - canvasWidth: Int, - canvasHeight: Int - ): CloseableReference? { - // Find the bitmap in cache memory (RAM) - val cache = bitmapCache.getCachedFrame(frameNumber) - if (cache?.isValid == true) { - prepareNextOnDemandFrame(frameNumber) - return cache - } - - // Check if bitmap should be cached - if (!isOnDemandFrame(frameNumber)) { - prepareFrames(canvasWidth, canvasHeight) {} - } - - if (onDemandBitmap?.isValidFor(frameNumber) == true) { - return onDemandBitmap?.bitmap - } - - return findNearestFrame(frameNumber) - } - - private fun prepareNextOnDemandFrame(lastFrameRendered: Int) { - if (fetchingOnDemand.getAndSet(true)) { - return - } - - val nextFrame = findNextOnDemandFrame(lastFrameRendered) - if (nextFrame != null && onDemandBitmap?.isValidFor(nextFrame) != true) { - val onDemandTask = - loadFrameTaskFactory.createOnDemandTask( - nextFrame, - { bitmapCache.getCachedFrame(it) }, - { bitmap -> - if (bitmap != null) { - onDemandBitmap = OnDemandFrame(nextFrame, bitmap) - } - fetchingOnDemand.set(false) - }) - AnimationLoaderExecutor.execute(onDemandTask) - } else { - fetchingOnDemand.set(false) - } - } - - /** Calculate if [frameNumber] have to be rendered by onDemand (CPU) */ - private fun isOnDemandFrame(frameNumber: Int): Boolean { - // If onDemandRatio > frame count, means that we disable onDemand generation - if (onDemandRatio > frameCount) { - return false - } - // Compare with 1 because we don't want to onDemand the first frame - return frameNumber % onDemandRatio == 1 - } - - private fun findNearestFrame(fromFrame: Int): CloseableReference? { - return (fromFrame downTo 0).asSequence().firstNotNullOfOrNull { - val frame = bitmapCache.getCachedFrame(it) - if (frame?.isValid == true) frame else null - } - } - - override fun onStop() { - onDemandBitmap?.close() - bitmapCache.clear() - } - - override fun clearFrames() { - bitmapCache.clear() - } - - /** Find next frame since [from] index which is missing in cache */ - @UiThread - private fun findNextOnDemandFrame(from: Int): Int? { - if (onDemandFrames.isEmpty()) { - return null - } - - return onDemandFrames.firstOrNull { it > from } ?: onDemandFrames.first() - } - - private fun calculateFrameSize(canvasWidth: Int, canvasHeight: Int): Size { - if (!downscaleFrameToDrawableDimensions) { - return Size(animationWidth, animationHeight) - } - - var bitmapWidth: Int = animationWidth - var bitmapHeight: Int = animationHeight - - // The maximum size for the bitmap is the size of the animation if the canvas is bigger - if (canvasWidth < animationWidth || canvasHeight < animationHeight) { - val ratioW = animationWidth.toDouble().div(animationHeight) - if (canvasHeight > canvasWidth) { - bitmapHeight = canvasHeight.coerceAtMost(animationHeight) - bitmapWidth = bitmapHeight.times(ratioW).toInt() - } else { - bitmapWidth = canvasWidth.coerceAtMost(animationWidth) - bitmapHeight = bitmapWidth.div(ratioW).toInt() - } - } - - return Size(bitmapWidth, bitmapHeight) - } - - companion object { - private val FETCH_FIRST_CACHE_DELAY_MS = 500 - private val FETCH_FULL_ANIMATION_CACHE_DELAY_MS = TimeUnit.SECONDS.toMillis(5) - } -} - -private class Size(val width: Int, val height: Int) - -private class OnDemandFrame(val frameNumber: Int, val bitmap: CloseableReference) : - Closeable { - fun isValidFor(frameNumber: Int): Boolean = this.frameNumber == frameNumber && bitmap.isValid - - override fun close() { - bitmap.close() - } -} diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTask.kt b/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTask.kt deleted file mode 100644 index c52ee6fe5f..0000000000 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTask.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.fresco.animation.bitmap.preparation.loadframe - -import android.graphics.Bitmap -import com.facebook.common.references.CloseableReference -import com.facebook.fresco.animation.bitmap.BitmapFrameRenderer -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFramePriorityTask.Priority -import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory - -/** - * This task render and save in a Map all the bitmap frames from Frame 0 until [untilFrame]. Once - * the render is finished, the bitmaps are provided via [output] - */ -class LoadFrameTask( - private val width: Int, - private val height: Int, - private val untilFrame: Int, - override val priority: Priority, - private val output: LoadFrameOutput, - private val platformBitmapFactory: PlatformBitmapFactory, - private val bitmapFrameRenderer: BitmapFrameRenderer, -) : LoadFramePriorityTask { - - private val bitmapConfig = Bitmap.Config.ARGB_8888 - - override fun run() { - val frameCollection = mutableMapOf>() - val canvasBitmapFrame = platformBitmapFactory.createBitmap(width, height, bitmapConfig) - - // Animation frames have to be render incrementally - (0 until untilFrame).forEach { frameNumber -> - var currentFrame: Bitmap? = null - var renderSucceed = false - - // Render frame - if (CloseableReference.isValid(canvasBitmapFrame)) { - currentFrame = canvasBitmapFrame.get() - renderSucceed = bitmapFrameRenderer.renderFrame(frameNumber, currentFrame) - } - - if (currentFrame == null || !renderSucceed) { - CloseableReference.closeSafely(canvasBitmapFrame) - frameCollection.values.forEach { CloseableReference.closeSafely(it) } - output.onFail() - return@forEach - } - - // Save frame - val copyFrame = platformBitmapFactory.createBitmap(currentFrame) - frameCollection[frameNumber] = copyFrame - } - - CloseableReference.closeSafely(canvasBitmapFrame) - output.onSuccess(frameCollection) - } -} diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTaskFactory.kt b/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTaskFactory.kt deleted file mode 100644 index b6f2bf1158..0000000000 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadFrameTaskFactory.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.fresco.animation.bitmap.preparation.loadframe - -import android.graphics.Bitmap -import com.facebook.common.references.CloseableReference -import com.facebook.fresco.animation.bitmap.BitmapFrameRenderer -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFramePriorityTask.Priority -import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory - -/** This factory provides task for the animation and set the priority on each task */ -class LoadFrameTaskFactory( - private val platformBitmapFactory: PlatformBitmapFactory, - private val bitmapFrameRenderer: BitmapFrameRenderer, -) { - - fun createFirstFrameTask( - width: Int, - height: Int, - output: LoadFrameOutput, - ): LoadFrameTask { - return LoadFrameTask( - width = width, - height = height, - untilFrame = 1, - priority = Priority.HIGH, - output = output, - platformBitmapFactory = platformBitmapFactory, - bitmapFrameRenderer = bitmapFrameRenderer) - } - - fun createLoadFullAnimationTask( - width: Int, - height: Int, - frameCount: Int, - output: LoadFrameOutput, - ): LoadFrameTask { - return LoadFrameTask( - width = width, - height = height, - untilFrame = frameCount, - priority = Priority.LOW, - output = output, - platformBitmapFactory = platformBitmapFactory, - bitmapFrameRenderer = bitmapFrameRenderer) - } - - fun createOnDemandTask( - frameNumber: Int, - getCachedBitmap: (Int) -> CloseableReference?, - output: (CloseableReference?) -> Unit, - ): LoadOnDemandFrameTask { - return LoadOnDemandFrameTask( - frameNumber = frameNumber, - getCachedBitmap = getCachedBitmap, - priority = Priority.MEDIUM, - output = output, - platformBitmapFactory = platformBitmapFactory, - bitmapFrameRenderer = bitmapFrameRenderer, - ) - } -} - -interface LoadFrameOutput { - fun onSuccess(frames: Map>) - - fun onFail() -} diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadOnDemandFrameTask.kt b/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadOnDemandFrameTask.kt deleted file mode 100644 index 42048a550d..0000000000 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/bitmap/preparation/loadframe/LoadOnDemandFrameTask.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.fresco.animation.bitmap.preparation.loadframe - -import android.graphics.Bitmap -import com.facebook.common.references.CloseableReference -import com.facebook.fresco.animation.bitmap.BitmapFrameRenderer -import com.facebook.fresco.animation.bitmap.preparation.loadframe.LoadFramePriorityTask.Priority -import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory - -/** - * Render the frame given [frameNumber]. We have to find the nearest cached bitmap using - * [getCachedBitmap] to start with, and the apply deltas until to render the [frameNumber] desired. - * The final bitmap will be sent via [output] - */ -class LoadOnDemandFrameTask( - private val frameNumber: Int, - private val getCachedBitmap: (Int) -> CloseableReference?, - override val priority: Priority, - private val output: (CloseableReference?) -> Unit, - private val platformBitmapFactory: PlatformBitmapFactory, - private val bitmapFrameRenderer: BitmapFrameRenderer, -) : LoadFramePriorityTask { - - override fun run() { - val nearestFrame = - (frameNumber downTo 0) - .asSequence() - .mapNotNull { - val bitmap = getCachedBitmap(it) ?: return@mapNotNull null - Pair(it, bitmap) - } - .firstOrNull() ?: return exit(null) - - val canvasBitmap = platformBitmapFactory.createBitmap(nearestFrame.second.get()) - (nearestFrame.first + 1..frameNumber).forEach { - bitmapFrameRenderer.renderFrame(it, canvasBitmap.get()) - } - exit(canvasBitmap) - } - - private fun exit(bitmap: CloseableReference?) { - output.invoke(bitmap) - } -} diff --git a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/animated/factory/AnimatedFactoryProvider.kt b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/animated/factory/AnimatedFactoryProvider.kt index 66eb952200..6fe920708e 100644 --- a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/animated/factory/AnimatedFactoryProvider.kt +++ b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/animated/factory/AnimatedFactoryProvider.kt @@ -10,7 +10,6 @@ package com.facebook.imagepipeline.animated.factory import com.facebook.cache.common.CacheKey import com.facebook.common.executors.SerialExecutorService import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory -import com.facebook.imagepipeline.cache.AnimatedCache import com.facebook.imagepipeline.cache.CountingMemoryCache import com.facebook.imagepipeline.core.ExecutorSupplier import com.facebook.imagepipeline.image.CloseableImage @@ -25,10 +24,8 @@ object AnimatedFactoryProvider { platformBitmapFactory: PlatformBitmapFactory?, executorSupplier: ExecutorSupplier?, backingCache: CountingMemoryCache?, - animatedCache: AnimatedCache, downscaleFrameToDrawableDimensions: Boolean, useBalancedAnimationStrategy: Boolean, - balancedStrategyPreparationMs: Int, animationFpsLimit: Int, serialExecutorService: ExecutorService? ): AnimatedFactory? { @@ -40,21 +37,17 @@ object AnimatedFactoryProvider { PlatformBitmapFactory::class.java, ExecutorSupplier::class.java, CountingMemoryCache::class.java, - AnimatedCache::class.java, java.lang.Boolean.TYPE, java.lang.Boolean.TYPE, Integer.TYPE, - Integer.TYPE, SerialExecutorService::class.java) impl = constructor.newInstance( platformBitmapFactory, executorSupplier, backingCache, - animatedCache, downscaleFrameToDrawableDimensions, useBalancedAnimationStrategy, - balancedStrategyPreparationMs, animationFpsLimit, serialExecutorService) as AnimatedFactory } catch (e: Throwable) { diff --git a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/cache/AnimatedCache.kt b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/cache/AnimatedCache.kt deleted file mode 100644 index 63cfee0159..0000000000 --- a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/cache/AnimatedCache.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.imagepipeline.cache - -import android.graphics.Bitmap -import com.facebook.common.references.CloseableReference -import com.facebook.common.util.ByteConstants.MB -import com.facebook.imageutils.BitmapUtil -import java.io.Closeable -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.TimeUnit - -/** Store frames from animations which are being displayed at this moment */ -class AnimatedCache private constructor(memoryMB: Int) { - - private val sizeBytes = memoryMB * MB - - /** % Memory for animations which were recently used, but they are not running now */ - private val evictionRatio = if (memoryMB < 90) 0.0f else 0.30f - - /** 10% Memory is the maximum size of an animation */ - private val maxCacheEntrySize = sizeBytes.times(0.1).toInt() - - private val lruCache = - LruCountingMemoryCache( - { it.sizeBytes }, - { it.suggestedTrimRatio }, - { - MemoryCacheParams( - maxCacheSize = sizeBytes, - maxCacheEntries = Int.MAX_VALUE, - maxEvictionQueueSize = sizeBytes.times(evictionRatio).toInt(), - maxEvictionQueueEntries = EVICTION_QUEUE, - maxCacheEntrySize = maxCacheEntrySize, - paramsCheckIntervalMs = TimeUnit.SECONDS.toMillis(5)) - }, - null, - false, - false, - ) - - fun getSize(key: String): Int = lruCache.sizeInBytes - - fun findAnimation(key: String): CloseableReference? = lruCache[key] - - fun saveAnimation( - key: String, - animationFrames: AnimationFrames - ): CloseableReference? { - return lruCache.cache(key, CloseableReference.of(animationFrames)) - } - - fun removeAnimation(key: String) { - lruCache.removeAll { cacheKey -> key == cacheKey } - } - - companion object { - private const val EVICTION_QUEUE = 50 - private var instance: AnimatedCache? = null - - @JvmStatic - fun getInstance(memoryMB: Int): AnimatedCache = - instance ?: AnimatedCache(memoryMB).also { instance = it } - } -} - -/** - * Represents the bitmaps of one animation. - * - * @param bitmapsByFrame are amount of final bitmap that we will render given a frame index. These - * amount could be less than original animation if the animation were reduced - * @param realToCompressIndexMap If animation was reduced, this map describes the equivalence - * between original frame number and reduced frame number - */ -class AnimationFrames( - bitmapsByFrame: Map>, - private val realToCompressIndexMap: Map -) : Closeable { - private val concurrentFrames = ConcurrentHashMap(bitmapsByFrame) - val frames: Map> - get() = concurrentFrames.filter { (_, frame) -> frame.isValidBitmap() } - - /** Calculate the size of animation */ - val sizeBytes: Int = - bitmapsByFrame.values.sumOf { - if (it.isValid) { - BitmapUtil.getSizeInBytes(it.get()) - } else 0 - } - - /** - * Return the bitmap (if exists) given a the animation frame. - * - * @param frameIndex Frame index in original animation (not reduced animation frame number). - * @return Bitmap for [frameIndex] if it is available - */ - fun getFrame(frameIndex: Int): CloseableReference? { - val frame = - if (realToCompressIndexMap.isEmpty()) { - concurrentFrames[frameIndex] - } else { - val reducedIndex = realToCompressIndexMap[frameIndex] ?: return null - concurrentFrames[reducedIndex] - } - - return if (frame?.isValidBitmap() == true) frame else null - } - - /** Release the stored bitmaps */ - override fun close() { - concurrentFrames.values.forEach { it.close() } - concurrentFrames.clear() - } - - private fun CloseableReference.isValidBitmap() = isValid && !get().isRecycled -} diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt index 48902b41f4..1fe9f5d73d 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt @@ -27,7 +27,6 @@ import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.platform.PlatformDecoderOptions import com.facebook.imageutils.BitmapUtil -import kotlin.jvm.JvmField /** * Encapsulates additional elements of the [ImagePipelineConfig] which are currently in an @@ -45,9 +44,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) { val useDownsamplingRatioForResizing: Boolean val useBitmapPrepareToDraw: Boolean val useBalancedAnimationStrategy: Boolean - val balancedStrategyPreparationMs: Int val bitmapPrepareToDrawMinSizeBytes: Int - val animatedCacheMemoryPercentage: Int val bitmapPrepareToDrawMaxSizeBytes: Int val bitmapPrepareToDrawForPrefetch: Boolean val maxBitmapSize: Int @@ -87,8 +84,6 @@ class ImagePipelineExperiments private constructor(builder: Builder) { @JvmField var useDownsamplingRatioForResizing = false @JvmField var useBitmapPrepareToDraw = false @JvmField var useBalancedAnimationStrategy = false - @JvmField var balancedStrategyPreparationMs = 10000 - @JvmField var animatedCacheMemoryPercentage = 40 @JvmField var bitmapPrepareToDrawMinSizeBytes = 0 @JvmField var bitmapPrepareToDrawMaxSizeBytes = 0 @@ -223,18 +218,6 @@ class ImagePipelineExperiments private constructor(builder: Builder) { this.useBalancedAnimationStrategy = useBalancedAnimationStrategy } - /** Indicates needed ms to extract a frame using CPU */ - fun setBalancedStrategyPreparationMs(balancedStrategyPreparationMs: Int) = asBuilder { - this.balancedStrategyPreparationMs = balancedStrategyPreparationMs - } - - /** - * Maximum heap memory percentage available for caching bitmaps from animated asset (WebP, Gif) - */ - fun setAnimatedCacheMemoryPercentage(animatedCacheMemoryPercentage: Int) = asBuilder { - this.animatedCacheMemoryPercentage = animatedCacheMemoryPercentage - } - /** * Sets the maximum bitmap size use to compute the downsampling value when decoding Jpeg images. */ @@ -421,8 +404,6 @@ class ImagePipelineExperiments private constructor(builder: Builder) { useDownsamplingRatioForResizing = builder.useDownsamplingRatioForResizing useBitmapPrepareToDraw = builder.useBitmapPrepareToDraw useBalancedAnimationStrategy = builder.useBalancedAnimationStrategy - balancedStrategyPreparationMs = builder.balancedStrategyPreparationMs - animatedCacheMemoryPercentage = builder.animatedCacheMemoryPercentage bitmapPrepareToDrawMinSizeBytes = builder.bitmapPrepareToDrawMinSizeBytes bitmapPrepareToDrawMaxSizeBytes = builder.bitmapPrepareToDrawMaxSizeBytes bitmapPrepareToDrawForPrefetch = builder.bitmapPrepareToDrawForPrefetch diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java index a0789e4e8b..b4c7d41e4c 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java @@ -7,8 +7,6 @@ package com.facebook.imagepipeline.core; -import static com.facebook.common.util.ByteConstants.MB; - import android.content.Context; import android.os.Build; import com.facebook.cache.common.CacheKey; @@ -24,7 +22,6 @@ import com.facebook.imagepipeline.animated.factory.AnimatedFactoryProvider; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactoryProvider; -import com.facebook.imagepipeline.cache.AnimatedCache; import com.facebook.imagepipeline.cache.BufferedDiskCache; import com.facebook.imagepipeline.cache.CountingMemoryCache; import com.facebook.imagepipeline.cache.EncodedCountingMemoryCacheFactory; @@ -130,7 +127,6 @@ public static synchronized void shutDown() { private final ImagePipelineConfigInterface mConfig; private final CloseableReferenceFactory mCloseableReferenceFactory; @Nullable private CountingMemoryCache mBitmapCountingMemoryCache; - @Nullable private AnimatedCache mAnimatedCache; @Nullable private InstrumentedMemoryCache mBitmapMemoryCache; @Nullable private CountingMemoryCache mEncodedCountingMemoryCache; @Nullable private InstrumentedMemoryCache mEncodedMemoryCache; @@ -174,10 +170,8 @@ private AnimatedFactory getAnimatedFactory() { getPlatformBitmapFactory(), mConfig.getExecutorSupplier(), getBitmapCountingMemoryCache(), - getAnimatedCache(mConfig.getExperiments().getAnimatedCacheMemoryPercentage()), mConfig.getExperiments().getDownscaleFrameToDrawableDimensions(), mConfig.getExperiments().getUseBalancedAnimationStrategy(), - mConfig.getExperiments().getBalancedStrategyPreparationMs(), mConfig.getExperiments().getAnimationRenderFpsLimit(), mConfig.getExecutorServiceForAnimatedImages()); } @@ -206,15 +200,6 @@ public CountingMemoryCache getBitmapCountingMemoryCach return mBitmapCountingMemoryCache; } - public AnimatedCache getAnimatedCache(int memoryPercentage) { - if (mAnimatedCache == null) { - final long maxMemory = Math.min(Runtime.getRuntime().maxMemory(), Integer.MAX_VALUE); - long cacheSizeMb = (maxMemory / 100 * memoryPercentage) / MB; - mAnimatedCache = AnimatedCache.getInstance((int) cacheSizeMb); - } - return mAnimatedCache; - } - public InstrumentedMemoryCache getBitmapMemoryCache() { if (mBitmapMemoryCache == null) { MemoryCache backingCache = getBitmapCountingMemoryCache();