Created
July 21, 2023 12:26
-
-
Save LennyLizowzskiy/e55f86b912ebb13b4196b472a577c27a to your computer and use it in GitHub Desktop.
Jetpack Compose / Composable to Bitmap converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.app.Activity | |
import android.content.Context | |
import android.content.ContextWrapper | |
import android.graphics.Bitmap | |
import android.os.Handler | |
import android.os.Looper | |
import android.view.PixelCopy | |
import android.view.View | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.interaction.MutableInteractionSource | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Spacer | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.DisposableEffect | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.Stable | |
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.geometry.Rect | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.asImageBitmap | |
import androidx.compose.ui.layout.boundsInWindow | |
import androidx.compose.ui.layout.onGloballyPositioned | |
import androidx.compose.ui.platform.LocalView | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | |
import kotlin.math.roundToInt | |
@Stable | |
interface BitmappableScope { | |
@Stable | |
suspend fun convertContentToBitmap(): Bitmap | |
} | |
@Stable | |
private class BitmappableScopeImpl( | |
val view: View, | |
val bounds: Rect | |
) : BitmappableScope { | |
@Stable | |
override suspend fun convertContentToBitmap() = view.clipContent(bounds) | |
} | |
@Composable | |
fun Bitmappable( | |
modifier: Modifier = Modifier, | |
content: @Composable BitmappableScope.() -> Unit | |
) { | |
var contentBounds by remember { | |
mutableStateOf<Rect?>(null) | |
} | |
Box( | |
modifier = modifier | |
.onGloballyPositioned { | |
contentBounds = it.boundsInWindow() | |
} | |
) { | |
val view = LocalView.current | |
BitmappableScopeImpl(view, contentBounds ?: Rect.Zero).content() | |
} | |
} | |
@Stable | |
fun Context.getActivity(): Activity? = | |
when (this) { | |
is Activity -> this | |
is ContextWrapper -> this.getActivity() | |
else -> null | |
} | |
@Stable | |
fun View.clipContent( | |
bounds: Rect | |
): Bitmap { | |
val bitmap = Bitmap.createBitmap( | |
bounds.width.roundToInt(), | |
bounds.height.roundToInt(), | |
Bitmap.Config.ARGB_8888 | |
) | |
PixelCopy.request( // Android Oreo+ | |
checkNotNull(context.getActivity()).window, | |
android.graphics.Rect( | |
bounds.left.toInt(), | |
bounds.top.toInt(), | |
bounds.right.toInt(), | |
bounds.bottom.toInt() | |
), | |
bitmap, {}, Handler(Looper.getMainLooper()) | |
) | |
return bitmap | |
} | |
@Preview | |
@Composable | |
fun BitmappablePreview() { | |
var bitmap by remember { | |
mutableStateOf<Bitmap?>(null) | |
} | |
DisposableEffect(Unit) { | |
onDispose { | |
bitmap?.recycle() | |
} | |
} | |
var runConverter by remember { | |
mutableStateOf(false) | |
} | |
Column( | |
modifier = Modifier.padding(50.dp) | |
) { | |
Bitmappable( | |
modifier = Modifier | |
.clickable( | |
indication = null, | |
interactionSource = remember { MutableInteractionSource() } | |
) { | |
runConverter = true | |
} | |
) { | |
LaunchedEffect(runConverter) { | |
if (runConverter) { | |
bitmap = convertContentToBitmap() | |
} | |
} | |
Box( | |
Modifier | |
.size(100.dp) | |
.background(Color.Yellow) | |
) | |
} | |
Spacer(Modifier.height(16.dp)) | |
Box { | |
if (bitmap != null) { | |
Image(bitmap = bitmap!!.asImageBitmap(), contentDescription = null) | |
} else { | |
Box(Modifier.size(100.dp)) { | |
Text(text = "NO CONTENT", modifier = Modifier.size(100.dp)) | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment