Prerender icon layers to bitmap

This commit is contained in:
MM20
2023-05-15 16:49:10 +02:00
parent 40d53720e9
commit 3f9db4751f
3 changed files with 207 additions and 29 deletions
+1
View File
@@ -51,5 +51,6 @@ dependencies {
implementation(project(":core:ktx"))
implementation(project(":core:i18n"))
implementation(project(":libs:material-color-utilities"))
}
@@ -1,12 +1,112 @@
package de.mm20.launcher2.icons
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import androidx.core.graphics.withScale
import de.mm20.launcher2.ktx.drawWithColorFilter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import palettes.TonalPalette
sealed interface LauncherIcon
data class LauncherIconRenderSettings(
val size: Int,
val fgThemeColor: Int,
val bgThemeColor: Int,
val fgTone: Int,
val bgTone: Int,
)
data class StaticLauncherIcon(
val foregroundLayer: LauncherIconLayer,
val backgroundLayer: LauncherIconLayer,
): LauncherIcon
) : LauncherIcon {
private var cachedBitmap: Bitmap? = null
private var cachedRenderSettings: LauncherIconRenderSettings? = null
private var renderSemaphore = Semaphore(1)
interface DynamicLauncherIcon: LauncherIcon {
fun getCachedBitmap(settings: LauncherIconRenderSettings): Bitmap? {
return if (cachedRenderSettings == settings) cachedBitmap else null
}
/**
* Render this icon to a bitmap.
*/
suspend fun render(settings: LauncherIconRenderSettings): Bitmap {
val cachedBmp = cachedBitmap
if (cachedRenderSettings == settings && cachedBmp != null) return cachedBmp
val bmp = withContext(Dispatchers.Default) {
renderSemaphore.withPermit {
val bmp =
if (cachedBmp == null || cachedBmp.width != settings.size || cachedBmp.height != settings.size) {
Bitmap.createBitmap(settings.size, settings.size, Bitmap.Config.ARGB_8888)!!
} else cachedBmp
val canvas = Canvas(bmp)
canvas.drawRect(
Rect(0, 0, canvas.width, canvas.height), Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
})
renderLayer(canvas, backgroundLayer, settings.bgThemeColor, settings.bgTone)
renderLayer(canvas, foregroundLayer, settings.fgThemeColor, settings.fgTone)
cachedBitmap = bmp
cachedRenderSettings = settings
bmp
}
}
return bmp
}
private fun renderLayer(canvas: Canvas, layer: LauncherIconLayer, themeColor: Int, tone: Int) {
when(layer) {
is ColorLayer -> {
val paint = Paint()
paint.color = if (layer.color == 0) themeColor else getTone(layer.color, tone)
canvas.drawRect(Rect(0, 0, canvas.width, canvas.height), paint)
}
is StaticIconLayer -> {
canvas.withScale(
layer.scale,
layer.scale,
canvas.width / 2f,
canvas.height / 2f,
) {
layer.icon.bounds = Rect(0, 0, canvas.width, canvas.height)
layer.icon.draw(canvas)
}
}
is TintedIconLayer -> {
val color = if (layer.color == 0) themeColor else getTone(layer.color, tone)
canvas.withScale(
layer.scale,
layer.scale,
canvas.width / 2f,
canvas.height / 2f,
) {
layer.icon.bounds = Rect(0, 0, canvas.width, canvas.height)
layer.icon.drawWithColorFilter(canvas,
PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
)
}
}
else -> {}
}
}
private fun getTone(argb: Int, tone: Int): Int {
return TonalPalette
.fromInt(argb)
.tone(tone)
}
}
interface DynamicLauncherIcon : LauncherIcon {
suspend fun getIcon(time: Long): StaticLauncherIcon
}