Skip to content

Update Bitmap to image snippet to use new Graphics Layers #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import com.example.compose.snippets.components.ScaffoldExample
import com.example.compose.snippets.components.SliderExamples
import com.example.compose.snippets.components.SwitchExamples
import com.example.compose.snippets.graphics.ApplyPolygonAsClipImage
import com.example.compose.snippets.graphics.BitmapFromComposableSnippet
import com.example.compose.snippets.graphics.BitmapFromComposableFullSnippet
import com.example.compose.snippets.graphics.BrushExamplesScreen
import com.example.compose.snippets.images.ImageExamplesScreen
import com.example.compose.snippets.landing.LandingScreen
Expand Down Expand Up @@ -70,7 +70,7 @@ class SnippetsActivity : ComponentActivity() {
Destination.BrushExamples -> BrushExamplesScreen()
Destination.ImageExamples -> ImageExamplesScreen()
Destination.AnimationQuickGuideExamples -> AnimationExamplesScreen()
Destination.ScreenshotExample -> BitmapFromComposableSnippet()
Destination.ScreenshotExample -> BitmapFromComposableFullSnippet()
Destination.ComponentsExamples -> ComponentsScreen {
navController.navigate(
it.route
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ import android.content.Context
import android.content.Intent
import android.content.Intent.createChooser
import android.graphics.Bitmap
import android.graphics.Picture
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -44,18 +45,17 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.draw
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
Expand Down Expand Up @@ -87,14 +87,44 @@ import kotlinx.coroutines.suspendCancellableCoroutine
* limitations under the License.
*/

@Preview
@Composable
private fun CreateBitmapFromGraphicsLayer() {
// [START android_compose_graphics_layer_bitmap_basics]
val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
modifier = Modifier
.drawWithContent {
// call record to capture the content in the graphics layer
graphicsLayer.record {
// draw the contents of the composable into the graphics layer
[email protected]()
}
// draw the graphics layer on the visible canvas
drawLayer(graphicsLayer)
}
.clickable {
coroutineScope.launch {
val bitmap = graphicsLayer.toImageBitmap()
// do something with the newly acquired bitmap
}
}
.background(Color.White)
) {
Text("Hello Android", fontSize = 26.sp)
}
// [END android_compose_graphics_layer_bitmap_basics]
}

@OptIn(ExperimentalPermissionsApi::class)
@Preview
@Composable
fun BitmapFromComposableSnippet() {
fun BitmapFromComposableFullSnippet() {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val picture = remember { Picture() }
val graphicsLayer = rememberGraphicsLayer()

val writeStorageAccessState = rememberMultiplePermissionsState(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Expand All @@ -104,14 +134,15 @@ fun BitmapFromComposableSnippet() {
listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
)

// This logic should live in your ViewModel - trigger a side effect to invoke URI sharing.
// checks permissions granted, and then saves the bitmap from a Picture that is already capturing content
// and shares it with the default share sheet.
fun shareBitmapFromComposable() {
if (writeStorageAccessState.allPermissionsGranted) {
coroutineScope.launch {
val bitmap = createBitmapFromPicture(picture)
val uri = bitmap.saveToDisk(context)
val bitmap = graphicsLayer.toImageBitmap()
val uri = bitmap.asAndroidBitmap().saveToDisk(context)
shareBitmap(context, uri)
}
} else if (writeStorageAccessState.shouldShowRationale) {
Expand Down Expand Up @@ -140,39 +171,22 @@ fun BitmapFromComposableSnippet() {
}
}
) { padding ->
// [START android_compose_draw_into_bitmap]
Column(
modifier = Modifier
.padding(padding)
.fillMaxSize()
.drawWithCache {
// Example that shows how to redirect rendering to an Android Picture and then
// draw the picture into the original destination
val width = this.size.width.toInt()
val height = this.size.height.toInt()

onDrawWithContent {
val pictureCanvas =
androidx.compose.ui.graphics.Canvas(
picture.beginRecording(
width,
height
)
)
// requires at least 1.6.0-alpha01+
draw(this, this.layoutDirection, pictureCanvas, this.size) {
graphicsLayer.record {
[email protected]()
}
picture.endRecording()

drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
drawLayer(graphicsLayer)
}
}

) {
ScreenContentToCapture()
}
// [END android_compose_draw_into_bitmap]
}
}

Expand Down Expand Up @@ -207,25 +221,6 @@ private fun ScreenContentToCapture() {
}
}

private fun createBitmapFromPicture(picture: Picture): Bitmap {
// [START android_compose_draw_into_bitmap_convert_picture]
val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Bitmap.createBitmap(picture)
} else {
val bitmap = Bitmap.createBitmap(
picture.width,
picture.height,
Bitmap.Config.ARGB_8888
)
val canvas = android.graphics.Canvas(bitmap)
canvas.drawColor(android.graphics.Color.WHITE)
canvas.drawPicture(picture)
bitmap
}
// [END android_compose_draw_into_bitmap_convert_picture]
return bitmap
}

private suspend fun Bitmap.saveToDisk(context: Context): Uri {
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ private fun CustomSnapDistance() {
HorizontalPager(
state = pagerState,
pageSize = PageSize.Fixed(200.dp),
outOfBoundsPageCount = 10,
beyondViewportPageCount = 10,
flingBehavior = fling
) {
PagerSampleItem(page = it)
Expand Down
11 changes: 6 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ accompanist = "0.32.0"
androidGradlePlugin = "8.2.2"
androidx-activity-compose = "1.9.0-alpha03"
androidx-appcompat = "1.6.1"
androidx-compose-bom = "2024.01.00"
androidx-compose-bom = "2024.04.01"
androidx-compose-ui-test = "1.7.0-alpha03"
androidx-constraintlayout = "2.1.4"
androidx-constraintlayout-compose = "1.0.1"
androidx-coordinator-layout = "1.2.0"
Expand All @@ -13,16 +14,16 @@ androidx-fragment-ktx = "1.6.2"
androidx-glance-appwidget = "1.0.0"
androidx-lifecycle-compose = "2.7.0"
androidx-lifecycle-runtime-compose = "2.7.0"
androidx-navigation = "2.7.6"
androidx-navigation = "2.7.7"
androidx-paging = "3.2.1"
androidx-test = "1.5.0"
androidx-test-espresso = "3.5.1"
androidxHiltNavigationCompose = "1.1.0"
androidxHiltNavigationCompose = "1.2.0"
coil = "2.5.0"
# @keep
compileSdk = "34"
compose-compiler = "1.5.4"
compose-latest = "1.7.0-alpha03"
compose-latest = "1.7.0-alpha07"
coroutines = "1.7.3"
google-maps = "18.2.0"
gradle-versions = "0.51.0"
Expand Down Expand Up @@ -66,7 +67,7 @@ androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" }
androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-googlefonts = { module = "androidx.compose.ui:ui-text-google-fonts" }
androidx-graphics-shapes = "androidx.graphics:graphics-shapes:1.0.0-alpha04"
androidx-graphics-shapes = "androidx.graphics:graphics-shapes:1.0.0-alpha05"
androidx-compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test" }
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
Expand Down