blob: e2244d1d717ea534bc04e07ff656be51fb78fa6a [file] [log] [blame]
package com.airbnb.lottie.samples
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.Log
import com.airbnb.lottie.L
import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.util.*
import java.util.concurrent.ConcurrentHashMap
internal class BitmapPool {
private val semaphore = SuspendingSemaphore(MAX_RELEASED_BITMAPS)
private val bitmaps = Collections.synchronizedList(ArrayList<Bitmap>())
private val releasedBitmaps = ConcurrentHashMap<Bitmap, Bitmap>()
private val clearPaint by lazy {
Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
}
@ExperimentalCoroutinesApi
fun acquire(width: Int, height: Int): Bitmap {
if (width <= 0 || height <= 0) {
return TRANSPARENT_1X1_BITMAP
}
var blockedStartTime = System.currentTimeMillis()
semaphore.acquire()
val waitingTimeMs = System.currentTimeMillis() - blockedStartTime
if (waitingTimeMs > 100) {
Log.d(L.TAG, "Waited ${waitingTimeMs}ms for a bitmap.")
}
val bitmap = synchronized(bitmaps) {
bitmaps
.firstOrNull { it.width >= width && it.height >= height }
?.also { bitmaps.remove(it) }
} ?: createNewBitmap(width, height)
val croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height)
releasedBitmaps[croppedBitmap] = bitmap
Canvas(croppedBitmap).apply {
drawRect(0f, 0f, croppedBitmap.width.toFloat(), croppedBitmap.height.toFloat(), clearPaint)
}
return croppedBitmap
}
fun release(bitmap: Bitmap) {
if (bitmap == TRANSPARENT_1X1_BITMAP) {
return
}
val originalBitmap = releasedBitmaps.remove(bitmap) ?: throw IllegalArgumentException("Unable to find original bitmap.")
bitmaps += originalBitmap
semaphore.release()
}
private fun createNewBitmap(width: Int, height: Int): Bitmap {
Log.d("LottieTest", "Creating a new bitmap of $width x $height")
// Make the bitmap at least as large as the screen so we don't wind up with a fragmented pool of
// bitmap sizes. We'll crop the right size out of it before returning it in acquire().
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
}
companion object {
// The maximum number of bitmaps that are allowed out at a time.
// If this limit is reached a thread must wait for another bitmap to be returned.
// Bitmaps are expensive, and if we aren't careful we can easily allocate too many bitmaps
// since coroutines run parallelized.
private const val MAX_RELEASED_BITMAPS = 10
private val TRANSPARENT_1X1_BITMAP: Bitmap by lazy {
Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
}
}
}