From e49e38f10498964282db3529e5fc6ba2893d8bb0 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 18 Aug 2023 12:16:20 +0100 Subject: [PATCH 01/15] 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 4c4ec51a3..bdd0ad822 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 62d423942..b27b7e5aa 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 000000000..077fd73f7 --- /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 21a1e5e07..cf28c8f9b 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 000000000..0f243bbcd --- /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 02/15] 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 077fd73f7..dc81e993f 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 +} From df271d55e383f9db2f8b17464825c12fd0e138f9 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 18 Aug 2023 12:16:20 +0100 Subject: [PATCH 03/15] Added example for sharing bitmap from composables. --- compose/snippets/src/main/AndroidManifest.xml | 10 ++ .../compose/snippets/SnippetsActivity.kt | 2 + .../graphics/AdvancedGraphicsSnippets.kt | 153 ++++++++++++++++++ .../snippets/navigation/Destination.kt | 1 + .../src/main/res/xml/provider_paths.xml | 4 + 5 files changed, 170 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 4c4ec51a3..bdd0ad822 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 62d423942..b27b7e5aa 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 000000000..ccac9fbb6 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/AdvancedGraphicsSnippets.kt @@ -0,0 +1,153 @@ +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.border +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.Brush +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).border(Brush.horizontalGradient(Color.Green, Color.Red), t), + 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) +} \ 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 21a1e5e07..cf28c8f9b 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 000000000..0f243bbcd --- /dev/null +++ b/compose/snippets/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From ab0da68983aad4784ae84969f5f482025da046b5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 18 Aug 2023 13:06:22 +0100 Subject: [PATCH 04/15] Added example for sharing bitmap from composables. --- .../graphics/AdvancedGraphicsSnippets.kt | 115 ++++++++++++------ 1 file changed, 80 insertions(+), 35 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 6845d5c26..89531e431 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 @@ -27,19 +27,28 @@ import android.graphics.Picture import android.graphics.drawable.PictureDrawable import android.net.Uri import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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 +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache +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 @@ -48,9 +57,12 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity import androidx.core.content.FileProvider import com.example.compose.snippets.R +import kotlinx.coroutines.Dispatchers import java.io.File import kotlinx.coroutines.launch @@ -72,53 +84,86 @@ import kotlinx.coroutines.launch @Preview @Composable fun BitmapFromComposableSnippet() { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() val picture = remember { Picture() } - val context = LocalContext.current + Scaffold(floatingActionButton = { + FloatingActionButton(onClick = { + // TODO Move this logic to your ViewModel, + // Then trigger side effect with the result URI to share + coroutineScope.launch(Dispatchers.IO) { + val bitmap = createBitmapFromPicture(picture) + val uri = bitmap.saveToDisk(context) + shareBitmap(context, uri) + } + }) { + Icon(Icons.Default.Share, "share") + } + }) { 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 + ) + ) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() - val coroutineScope = rememberCoroutineScope() - // [START android_compose_draw_into_bitmap] + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + } + ) { + ScreenContentToCapture() + } + // [END android_compose_draw_into_bitmap] + } +} + +@Composable +private fun ScreenContentToCapture() { Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, 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) } - } - } + .background( + Brush.linearGradient( + listOf( + Color(0xFFF5D5C0), + Color(0xFFF8E8E3) + ) + ) + ) ) { - // [START_EXCLUDE] Image( - painterResource(id = R.drawable.dog), + painterResource(id = R.drawable.sunset), contentDescription = null, - modifier = Modifier.aspectRatio(1f), + modifier = Modifier + .aspectRatio(1f) + .padding(32.dp), 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] + Text( + "Into the Ocean depths", + fontSize = 18.sp + ) } - // [END android_compose_draw_into_bitmap] } suspend fun createBitmapFromPicture(picture: Picture): Bitmap { @@ -138,7 +183,7 @@ suspend fun createBitmapFromPicture(picture: Picture): Bitmap { private suspend fun Bitmap.saveToDisk(context: Context): Uri { val file = File( context.getExternalFilesDir("external_files"), - "screenshot${System.currentTimeMillis()}.png" + "screenshot-${System.currentTimeMillis()}.png" ) file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100) return FileProvider.getUriForFile( From 10490b1a0191e5aaecda910d0aae486d51add42d Mon Sep 17 00:00:00 2001 From: riggaroo Date: Fri, 18 Aug 2023 12:08:32 +0000 Subject: [PATCH 05/15] Apply Spotless --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 5 +---- 1 file changed, 1 insertion(+), 4 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 89531e431..702fe86ab 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 @@ -29,17 +29,14 @@ import android.net.Uri import androidx.compose.foundation.Image import androidx.compose.foundation.background 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 -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -62,8 +59,8 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity import androidx.core.content.FileProvider import com.example.compose.snippets.R -import kotlinx.coroutines.Dispatchers import java.io.File +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch /* From b0959e3187c20b0b2636e5a664d15d0c98bf5a94 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 18 Aug 2023 13:10:27 +0100 Subject: [PATCH 06/15] Added example for sharing bitmap from composables. --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 89531e431..dc15fae61 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 @@ -63,6 +63,7 @@ import androidx.core.content.ContextCompat.startActivity import androidx.core.content.FileProvider import com.example.compose.snippets.R import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope import java.io.File import kotlinx.coroutines.launch @@ -180,7 +181,7 @@ suspend fun createBitmapFromPicture(picture: Picture): Bitmap { return bitmap } -private suspend fun Bitmap.saveToDisk(context: Context): Uri { +private fun Bitmap.saveToDisk(context: Context): Uri { val file = File( context.getExternalFilesDir("external_files"), "screenshot-${System.currentTimeMillis()}.png" From e461bf13baa91a908d4cefcc999fa1bdd1e6c036 Mon Sep 17 00:00:00 2001 From: riggaroo Date: Fri, 18 Aug 2023 12:14:05 +0000 Subject: [PATCH 07/15] Apply Spotless --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a9526490e..789ac0d89 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 @@ -59,8 +59,8 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity import androidx.core.content.FileProvider import com.example.compose.snippets.R -import kotlinx.coroutines.Dispatchers import java.io.File +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch /* From 2b2008e49aa146608e1127b4da6e24880919d7c4 Mon Sep 17 00:00:00 2001 From: Yacine Rezgui Date: Fri, 18 Aug 2023 15:39:56 +0200 Subject: [PATCH 08/15] Add image in the shared storage instead of relying on a FileProvider (#142) * Add image in the shared storage instead of relying on a FileProvider * Add comment * Apply Spotless --------- Co-authored-by: yrezgui --- compose/snippets/build.gradle.kts | 2 + compose/snippets/src/main/AndroidManifest.xml | 14 +-- .../graphics/AdvancedGraphicsSnippets.kt | 114 +++++++++++++----- .../src/main/res/xml/provider_paths.xml | 4 - 4 files changed, 91 insertions(+), 43 deletions(-) delete mode 100644 compose/snippets/src/main/res/xml/provider_paths.xml diff --git a/compose/snippets/build.gradle.kts b/compose/snippets/build.gradle.kts index c2e5d9d7d..4a65136d5 100644 --- a/compose/snippets/build.gradle.kts +++ b/compose/snippets/build.gradle.kts @@ -114,6 +114,8 @@ dependencies { implementation(libs.accompanist.theme.adapter.material3) implementation(libs.accompanist.theme.adapter.material) + implementation(libs.accompanist.permissions) + implementation(libs.coil.kt.compose) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/compose/snippets/src/main/AndroidManifest.xml b/compose/snippets/src/main/AndroidManifest.xml index bdd0ad822..d8b063164 100644 --- a/compose/snippets/src/main/AndroidManifest.xml +++ b/compose/snippets/src/main/AndroidManifest.xml @@ -18,6 +18,9 @@ + + - - - - - \ No newline at end of file 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 789ac0d89..6ac0152d0 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 @@ -16,8 +16,7 @@ package com.example.compose.snippets.graphics -import android.R.attr.height -import android.R.attr.width +import android.Manifest import android.content.Context import android.content.Intent import android.content.Intent.createChooser @@ -25,7 +24,11 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Picture import android.graphics.drawable.PictureDrawable +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.layout.Arrangement @@ -38,6 +41,9 @@ import androidx.compose.material.icons.filled.Share import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -57,11 +63,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity -import androidx.core.content.FileProvider import com.example.compose.snippets.R +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.rememberMultiplePermissionsState import java.io.File +import kotlin.coroutines.resume import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine /* * Copyright 2022 The Android Open Source Project @@ -78,27 +87,58 @@ import kotlinx.coroutines.launch * See the License for the specific language governing permissions and * limitations under the License. */ +@OptIn(ExperimentalPermissionsApi::class) @Preview @Composable fun BitmapFromComposableSnippet() { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + val picture = remember { Picture() } - Scaffold(floatingActionButton = { - FloatingActionButton(onClick = { - // TODO Move this logic to your ViewModel, - // Then trigger side effect with the result URI to share - coroutineScope.launch(Dispatchers.IO) { - val bitmap = createBitmapFromPicture(picture) - val uri = bitmap.saveToDisk(context) - shareBitmap(context, uri) + + val writeStorageAccessState = rememberMultiplePermissionsState( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // No permissions are needed on Android 10+ to add files in the shared storage + emptyList() + } else { + listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + ) + + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) }, + floatingActionButton = { + FloatingActionButton(onClick = { + // TODO Move this logic to your ViewModel, + // Then trigger side effect with the result URI to share + if (writeStorageAccessState.allPermissionsGranted) { + coroutineScope.launch(Dispatchers.IO) { + val bitmap = createBitmapFromPicture(picture) + val uri = bitmap.saveToDisk(context) + shareBitmap(context, uri) + } + } else if (writeStorageAccessState.shouldShowRationale) { + coroutineScope.launch { + val result = snackbarHostState.showSnackbar( + message = "The storage permission is needed to save the image", + actionLabel = "Grant Access" + ) + + if (result == SnackbarResult.ActionPerformed) { + writeStorageAccessState.launchMultiplePermissionRequest() + } + } + } else { + writeStorageAccessState.launchMultiplePermissionRequest() + } + }) { + Icon(Icons.Default.Share, "share") } - }) { - Icon(Icons.Default.Share, "share") } - }) { padding -> + ) { padding -> // [START android_compose_draw_into_bitmap] Column( modifier = Modifier @@ -163,31 +203,49 @@ private fun ScreenContentToCapture() { } } -suspend fun createBitmapFromPicture(picture: Picture): Bitmap { +fun createBitmapFromPicture(picture: Picture): Bitmap { val pictureDrawable = PictureDrawable(picture) - val bitmap = - Bitmap.createBitmap( - pictureDrawable.intrinsicWidth, - pictureDrawable.intrinsicHeight, - Bitmap.Config.ARGB_8888 - ) + 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 fun Bitmap.saveToDisk(context: Context): Uri { +private suspend fun Bitmap.saveToDisk(context: Context): Uri { val file = File( - context.getExternalFilesDir("external_files"), + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "screenshot-${System.currentTimeMillis()}.png" ) + file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100) - return FileProvider.getUriForFile( - context, - context.applicationContext.packageName + ".provider", - file - ) + + return scanFilePath(context, file.path) ?: throw Exception("File could not be saved") +} + +/** + * We call [MediaScannerConnection] to index the newly created image inside MediaStore to be visible + * for other apps, as well as returning its [MediaStore] Uri + */ +private suspend fun scanFilePath(context: Context, filePath: String): Uri? { + return suspendCancellableCoroutine { continuation -> + MediaScannerConnection.scanFile( + context, + arrayOf(filePath), + arrayOf("image/png") + ) { _, scannedUri -> + if (scannedUri == null) { + continuation.cancel(Exception("File $filePath could not be scanned")) + } else { + continuation.resume(scannedUri) + } + } + } } private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) { diff --git a/compose/snippets/src/main/res/xml/provider_paths.xml b/compose/snippets/src/main/res/xml/provider_paths.xml deleted file mode 100644 index 0f243bbcd..000000000 --- a/compose/snippets/src/main/res/xml/provider_paths.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 01d5734b9ec58e4c1be7355f93754a0f8612ddc5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 21 Aug 2023 20:18:42 +0100 Subject: [PATCH 09/15] Remove additional PictureDrawable that isn't needed. --- .../compose/snippets/SnippetsActivity.kt | 2 + .../graphics/AdvancedGraphicsSnippets.kt | 63 +++++++++---------- 2 files changed, 33 insertions(+), 32 deletions(-) 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 b27b7e5aa..c3b0cce6d 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 @@ -17,6 +17,7 @@ package com.example.compose.snippets import android.os.Bundle +import android.os.StrictMode import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize @@ -37,6 +38,7 @@ import com.example.compose.snippets.ui.theme.SnippetsTheme class SnippetsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + StrictMode.enableDefaults() setContent { SnippetsTheme { val navController = rememberNavController() 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 6ac0152d0..efa541abd 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 @@ -23,7 +23,6 @@ import android.content.Intent.createChooser import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Picture -import android.graphics.drawable.PictureDrawable import android.media.MediaScannerConnection import android.net.Uri import android.os.Build @@ -94,10 +93,7 @@ fun BitmapFromComposableSnippet() { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - - val picture = remember { - Picture() - } + val picture = remember { Picture() } val writeStorageAccessState = rememberMultiplePermissionsState( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -107,33 +103,37 @@ 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(Dispatchers.IO) { + val bitmap = createBitmapFromPicture(picture) + val uri = bitmap.saveToDisk(context) + shareBitmap(context, uri) + } + } else if (writeStorageAccessState.shouldShowRationale) { + coroutineScope.launch { + val result = snackbarHostState.showSnackbar( + message = "The storage permission is needed to save the image", + actionLabel = "Grant Access" + ) + + if (result == SnackbarResult.ActionPerformed) { + writeStorageAccessState.launchMultiplePermissionRequest() + } + } + } else { + writeStorageAccessState.launchMultiplePermissionRequest() + } + } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, floatingActionButton = { FloatingActionButton(onClick = { - // TODO Move this logic to your ViewModel, - // Then trigger side effect with the result URI to share - if (writeStorageAccessState.allPermissionsGranted) { - coroutineScope.launch(Dispatchers.IO) { - val bitmap = createBitmapFromPicture(picture) - val uri = bitmap.saveToDisk(context) - shareBitmap(context, uri) - } - } else if (writeStorageAccessState.shouldShowRationale) { - coroutineScope.launch { - val result = snackbarHostState.showSnackbar( - message = "The storage permission is needed to save the image", - actionLabel = "Grant Access" - ) - - if (result == SnackbarResult.ActionPerformed) { - writeStorageAccessState.launchMultiplePermissionRequest() - } - } - } else { - writeStorageAccessState.launchMultiplePermissionRequest() - } + shareBitmapFromComposable() }) { Icon(Icons.Default.Share, "share") } @@ -203,17 +203,16 @@ private fun ScreenContentToCapture() { } } -fun createBitmapFromPicture(picture: Picture): Bitmap { - val pictureDrawable = PictureDrawable(picture) +private fun createBitmapFromPicture(picture: Picture): Bitmap { val bitmap = Bitmap.createBitmap( - pictureDrawable.intrinsicWidth, - pictureDrawable.intrinsicHeight, + picture.width, + picture.height, Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) canvas.drawColor(android.graphics.Color.WHITE) - canvas.drawPicture(pictureDrawable.picture) + canvas.drawPicture(picture) return bitmap } From ea2fbab8784b25107238a971f79064f3a52fe92b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 21 Aug 2023 21:01:13 +0100 Subject: [PATCH 10/15] Remove additional PictureDrawable that isn't needed. --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 1 + 1 file changed, 1 insertion(+) 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 efa541abd..891be4e1c 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 @@ -149,6 +149,7 @@ fun BitmapFromComposableSnippet() { // 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( From 73edad96bb7f93a338fce7febec6a7b106ce0304 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 21 Aug 2023 21:16:49 +0100 Subject: [PATCH 11/15] add example showing how to convert between picture -> bitmap --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 2 ++ 1 file changed, 2 insertions(+) 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 891be4e1c..c9a6b1578 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 @@ -205,6 +205,7 @@ private fun ScreenContentToCapture() { } private fun createBitmapFromPicture(picture: Picture): Bitmap { + // [START android_compose_draw_into_bitmap_convert_picture] val bitmap = Bitmap.createBitmap( picture.width, picture.height, @@ -214,6 +215,7 @@ private fun createBitmapFromPicture(picture: Picture): Bitmap { val canvas = Canvas(bitmap) canvas.drawColor(android.graphics.Color.WHITE) canvas.drawPicture(picture) + // [END android_compose_draw_into_bitmap_convert_picture] return bitmap } From c7de49a8ebe78c078c9f6710614b09edaf3d77ef Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 25 Aug 2023 17:29:47 +0100 Subject: [PATCH 12/15] Add graphicsLayer transformations --- .../graphics/AdvancedGraphicsSnippets.kt | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 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 c9a6b1578..4f703169a 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 @@ -26,8 +26,12 @@ import android.graphics.Picture import android.media.MediaScannerConnection import android.net.Uri import android.os.Build +import android.os.Bundle import android.os.Environment +import android.os.StrictMode import android.provider.MediaStore +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -35,10 +39,12 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -52,17 +58,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy import androidx.compose.ui.graphics.drawscope.draw import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity import com.example.compose.snippets.R +import com.example.compose.snippets.components.ProgressIndicatorExamples import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import java.io.File @@ -166,6 +178,7 @@ fun BitmapFromComposableSnippet() { drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } } } + ) { ScreenContentToCapture() } @@ -180,21 +193,30 @@ private fun ScreenContentToCapture() { verticalArrangement = Arrangement.Center, modifier = Modifier .fillMaxSize() + .graphicsLayer { + scaleX = 0.8f + } .background( Brush.linearGradient( listOf( - Color(0xFFF5D5C0), - Color(0xFFF8E8E3) + Color(0xFFC8E6C9), + Color(0xFFDDB6AA) ) ) ) ) { + ProgressIndicatorExamples() Image( painterResource(id = R.drawable.sunset), contentDescription = null, modifier = Modifier .aspectRatio(1f) - .padding(32.dp), + .padding(32.dp) + .graphicsLayer { + rotationY = 20f + compositingStrategy = CompositingStrategy.Offscreen + } + , contentScale = ContentScale.Crop ) Text( @@ -264,4 +286,4 @@ private fun shareBitmap(context: Context, uri: Uri) { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(context, createChooser(intent, "Share your image"), null) -} +} \ No newline at end of file From a24128eeaab76d8f69236c756394b3154b7ee0a0 Mon Sep 17 00:00:00 2001 From: riggaroo Date: Fri, 25 Aug 2023 16:31:51 +0000 Subject: [PATCH 13/15] Apply Spotless --- .../snippets/graphics/AdvancedGraphicsSnippets.kt | 13 ++----------- 1 file changed, 2 insertions(+), 11 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 4f703169a..4f9241cc3 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 @@ -26,12 +26,8 @@ import android.graphics.Picture import android.media.MediaScannerConnection import android.net.Uri import android.os.Build -import android.os.Bundle import android.os.Environment -import android.os.StrictMode import android.provider.MediaStore -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -44,7 +40,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -64,10 +59,7 @@ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.layout -import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -215,8 +207,7 @@ private fun ScreenContentToCapture() { .graphicsLayer { rotationY = 20f compositingStrategy = CompositingStrategy.Offscreen - } - , + }, contentScale = ContentScale.Crop ) Text( @@ -286,4 +277,4 @@ private fun shareBitmap(context: Context, uri: Uri) { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } startActivity(context, createChooser(intent, "Share your image"), null) -} \ No newline at end of file +} From f96b68b61db9222e50e89145f8a25fe03d7b7792 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 29 Aug 2023 12:57:08 +0100 Subject: [PATCH 14/15] add example showing how to convert between picture -> bitmap --- .../snippets/graphics/AdvancedGraphicsSnippets.kt | 10 +--------- 1 file changed, 1 insertion(+), 9 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 199a8d9ef..1b8190b9f 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 @@ -190,21 +190,13 @@ private fun ScreenContentToCapture() { ) ) ) - .graphicsLayer { - scaleX = 0.8f - } ) { Image( painterResource(id = R.drawable.sunset), contentDescription = null, modifier = Modifier .aspectRatio(1f) - .padding(32.dp) - // TODO REMOVE - .graphicsLayer { - rotationY = 20f - compositingStrategy = CompositingStrategy.Offscreen - }, + .padding(32.dp), contentScale = ContentScale.Crop ) Text( From 9f2709cac0e6d46041e8ec5989d51c2cca5f3a70 Mon Sep 17 00:00:00 2001 From: riggaroo Date: Tue, 29 Aug 2023 11:59:13 +0000 Subject: [PATCH 15/15] Apply Spotless --- .../compose/snippets/graphics/AdvancedGraphicsSnippets.kt | 1 - 1 file changed, 1 deletion(-) 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 1b8190b9f..63d3300ba 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 @@ -54,7 +54,6 @@ 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.graphicsLayer import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext