From e49e38f10498964282db3529e5fc6ba2893d8bb0 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 18 Aug 2023 12:16:20 +0100 Subject: [PATCH 1/2] Added example for sharing bitmap from composables. --- compose/snippets/src/main/AndroidManifest.xml | 10 ++ .../compose/snippets/SnippetsActivity.kt | 2 + .../graphics/AdvancedGraphicsSnippets.kt | 158 ++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + .../src/main/res/xml/provider_paths.xml | 4 + 5 files changed, 175 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt create mode 100644 compose/snippets/src/main/res/xml/provider_paths.xml diff --git a/compose/snippets/src/main/AndroidManifest.xml b/compose/snippets/src/main/AndroidManifest.xml index 4c4ec51a..bdd0ad82 100644 --- a/compose/snippets/src/main/AndroidManifest.xml +++ b/compose/snippets/src/main/AndroidManifest.xml @@ -58,6 +58,16 @@ + + + + \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 62d42394..b27b7e5a 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -27,6 +27,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.compose.snippets.animations.AnimationExamplesScreen +import com.example.compose.snippets.graphics.BitmapFromComposableSnippet import com.example.compose.snippets.graphics.BrushExamplesScreen import com.example.compose.snippets.images.ImageExamplesScreen import com.example.compose.snippets.landing.LandingScreen @@ -54,6 +55,7 @@ class SnippetsActivity : ComponentActivity() { Destination.BrushExamples -> BrushExamplesScreen() Destination.ImageExamples -> ImageExamplesScreen() Destination.AnimationQuickGuideExamples -> AnimationExamplesScreen() + Destination.ScreenshotExample -> BitmapFromComposableSnippet() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt new file mode 100644 index 00000000..077fd73f --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt @@ -0,0 +1,158 @@ +package com.example.compose.snippets.graphics + +import android.R.attr.height +import android.R.attr.width +import android.content.Context +import android.content.Intent +import android.content.Intent.createChooser +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Picture +import android.graphics.Rect +import android.graphics.drawable.PictureDrawable +import android.net.Uri +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.node.DrawModifierNode +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.core.content.ContextCompat.startActivity +import androidx.core.content.FileProvider +import com.example.compose.snippets.R +import kotlinx.coroutines.launch +import java.io.File + + +/* +* Copyright 2022 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +@Preview +@Composable +fun BitmapFromComposableSnippet() { + val picture = remember { + Picture() + } + val context = LocalContext.current + + val coroutineScope = rememberCoroutineScope() + // [START android_compose_draw_into_bitmap] + Column(modifier = Modifier + .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)) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() + + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + }) { + // [START_EXCLUDE] + Image( + painterResource(id = R.drawable.dog), + contentDescription = null, + modifier = Modifier.aspectRatio(1f), + contentScale = ContentScale.Crop + ) + Text("Sample Text") + IconButton(onClick = { + coroutineScope.launch { + val bitmap = createBitmapFromPicture(picture) + val uri = bitmap.saveToDisk(context) + shareBitmap(context, uri) + } + }) { + Icon(Icons.Default.Share, "share") + } + // [END_EXCLUDE] + } + // [END android_compose_draw_into_bitmap] +} + +suspend fun createBitmapFromPicture(picture: Picture): Bitmap { + val pictureDrawable = PictureDrawable(picture) + val bitmap = + Bitmap.createBitmap( + pictureDrawable.intrinsicWidth, + pictureDrawable.intrinsicHeight, + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + canvas.drawColor(android.graphics.Color.WHITE); + canvas.drawPicture(pictureDrawable.picture) + return bitmap +} +private suspend fun Bitmap.saveToDisk(context: Context): Uri { + val file = File( + context.getExternalFilesDir("external_files"), + "screenshot${System.currentTimeMillis()}.png" + ) + file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100) + return FileProvider.getUriForFile( + context, + context.applicationContext.packageName + ".provider", + file + ) +} +private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) { + outputStream().use { out -> + bitmap.compress(format, quality, out) + out.flush() + } +} +private fun shareBitmap(context: Context, uri: Uri) { + val intent = Intent(Intent.ACTION_SEND).apply { + type = "image/png" + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + startActivity(context, createChooser(intent, "Share your image"), null) +} + +class ScreenshotState { + +} + +sealed class ImageResult { + object Initial : ImageResult() + data class Error(val exception: Exception) : ImageResult() + data class Success(val data: Bitmap) : ImageResult() +} \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index 21a1e5e0..cf28c8f9 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -20,4 +20,5 @@ enum class Destination(val route: String, val title: String) { BrushExamples("brushExamples", "Brush Examples"), ImageExamples("imageExamples", "Image Examples"), AnimationQuickGuideExamples("animationExamples", "Animation Examples"), + ScreenshotExample("screenshotExample", "Screenshot Examples"), } diff --git a/compose/snippets/src/main/res/xml/provider_paths.xml b/compose/snippets/src/main/res/xml/provider_paths.xml new file mode 100644 index 00000000..0f243bbc --- /dev/null +++ b/compose/snippets/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 2ffa59ed20f6ff6833489b2be93a4367725cc249 Mon Sep 17 00:00:00 2001 From: riggaroo Date: Fri, 18 Aug 2023 11:18:29 +0000 Subject: [PATCH 2/2] Apply Spotless --- .../graphics/AdvancedGraphicsSnippets.kt | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt index 077fd73f..dc81e993 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.compose.snippets.graphics import android.R.attr.height @@ -8,7 +24,6 @@ import android.content.Intent.createChooser import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Picture -import android.graphics.Rect import android.graphics.drawable.PictureDrawable import android.net.Uri import androidx.compose.foundation.Image @@ -26,21 +41,18 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.draw import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.core.content.ContextCompat.startActivity import androidx.core.content.FileProvider import com.example.compose.snippets.R -import kotlinx.coroutines.launch import java.io.File - +import kotlinx.coroutines.launch /* * Copyright 2022 The Android Open Source Project @@ -67,24 +79,26 @@ fun BitmapFromComposableSnippet() { val coroutineScope = rememberCoroutineScope() // [START android_compose_draw_into_bitmap] - Column(modifier = Modifier - .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)) - draw(this, this.layoutDirection, pictureCanvas, this.size) { - this@onDrawWithContent.drawContent() - } - picture.endRecording() + Column( + modifier = Modifier + .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)) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() - drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } } - }) { + ) { // [START_EXCLUDE] Image( painterResource(id = R.drawable.dog), @@ -116,7 +130,7 @@ suspend fun createBitmapFromPicture(picture: Picture): Bitmap { Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) - canvas.drawColor(android.graphics.Color.WHITE); + canvas.drawColor(android.graphics.Color.WHITE) canvas.drawPicture(pictureDrawable.picture) return bitmap } @@ -147,12 +161,10 @@ private fun shareBitmap(context: Context, uri: Uri) { startActivity(context, createChooser(intent, "Share your image"), null) } -class ScreenshotState { - -} +class ScreenshotState sealed class ImageResult { object Initial : ImageResult() data class Error(val exception: Exception) : ImageResult() data class Success(val data: Bitmap) : ImageResult() -} \ No newline at end of file +}