Skip to content

Instantly share code, notes, and snippets.

@zoltish

zoltish/Blur.kt Secret

Last active January 11, 2023 12:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zoltish/1705dc27a3a35ef2731b2ef018aac2e0 to your computer and use it in GitHub Desktop.
Save zoltish/1705dc27a3a35ef2731b2ef018aac2e0 to your computer and use it in GitHub Desktop.
import android.content.Context
import android.graphics.Bitmap
import android.renderscript.Allocation
import android.renderscript.Allocation.MipmapControl.MIPMAP_NONE
import android.renderscript.Allocation.USAGE_SCRIPT
import android.renderscript.Allocation.createFromBitmap
import android.renderscript.Allocation.createTyped
import android.renderscript.Element.U8_4
import android.renderscript.RenderScript
import android.renderscript.RenderScript.RSMessageHandler
import android.renderscript.ScriptIntrinsicBlur
import android.renderscript.ScriptIntrinsicBlur.create
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import progression.presentation.design.animation.TransitionValue
import progression.presentation.design.token.Alpha
import progression.presentation.design.util.scrim
@Composable
fun Blur(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
) {
var bitmap by remember {
mutableStateOf<ImageBitmap?>(null)
}
Capture(
content = content,
transformation = BlurTransformation(),
callback = { capturedBitmap ->
bitmap = capturedBitmap.asImageBitmap()
},
)
TransitionValue(
value = bitmap,
content = { currentBitmap ->
Image(
modifier = modifier
.fillMaxSize()
.scrim(alpha = Alpha.low),
bitmap = currentBitmap,
contentDescription = null,
)
},
)
}
@JvmInline
private value class BlurTransformation(
private val radius: Float = 25f,
) : Transformation {
override suspend fun transform(
context: Context,
bitmap: Bitmap,
): Bitmap {
var script: RenderScript? = null
var input: Allocation? = null
var output: Allocation? = null
var blur: ScriptIntrinsicBlur? = null
try {
script = RenderScript.create(context)
script.messageHandler = RSMessageHandler()
input = createFromBitmap(
script,
bitmap,
MIPMAP_NONE,
USAGE_SCRIPT,
)
output = createTyped(
script,
input.type,
)
blur = create(
script,
U8_4(script),
)
blur.setInput(input)
blur.setRadius(radius)
blur.forEach(output)
output.copyTo(bitmap)
} finally {
script?.destroy()
input?.destroy()
output?.destroy()
blur?.destroy()
}
return bitmap
}
}
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Bitmap.createBitmap
import android.graphics.Canvas
import android.util.SparseArray
import android.view.View
import android.view.View.INVISIBLE
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.currentCompositeKeyHash
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun Capture(
key: Int = currentCompositeKeyHash,
transformation: Transformation = NoTransformation,
content: @Composable () -> Unit,
callback: (Bitmap) -> Unit,
) {
SubcomposeLayout { constraints ->
layout(
width = constraints.maxWidth,
height = constraints.maxHeight,
) {
val placeable = subcompose(0) {
val context = LocalContext.current
val view = remember {
ComposeView(context).apply { visibility = INVISIBLE }
}
LaunchedEffect(view) {
val bitmap = withContext(Default) {
view.createTransformedBitmap(
transformation = transformation,
width = constraints.maxWidth,
height = constraints.maxHeight,
)
}
callback(bitmap)
}
AndroidView(
factory = {
view.apply { setContent(content) }
},
)
}.single()
placeable
.measure(constraints)
.place(
x = 0,
y = 0,
)
}
}
}
private suspend fun View.createTransformedBitmap(
transformation: Transformation,
width: Int,
height: Int,
): Bitmap {
val bitmap = createBitmap(
width = width,
height = height,
)
return transformation.transform(
context = context,
bitmap = bitmap,
)
}
private fun View.createBitmap(
width: Int,
height: Int,
): Bitmap {
return createBitmap(
width,
height,
ARGB_8888,
).apply {
val canvas = Canvas(this)
draw(canvas)
}
}
@Stable
interface Transformation {
suspend fun transform(
context: Context,
bitmap: Bitmap,
): Bitmap
}
private object NoTransformation : Transformation {
override suspend fun transform(
context: Context,
bitmap: Bitmap,
): Bitmap {
return bitmap
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment