Skip to content

Commit 26d799f

Browse files
author
David Motsonashvili
committed
add imagen editing cases to the quickstart
1 parent d5adcac commit 26d799f

File tree

5 files changed

+100
-6
lines changed

5 files changed

+100
-6
lines changed

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package com.google.firebase.quickstart.ai
22

3+
import android.graphics.Bitmap
34
import com.google.firebase.ai.type.FunctionDeclaration
45
import com.google.firebase.ai.type.GenerativeBackend
6+
import com.google.firebase.ai.type.ImagenBackgroundMask
7+
import com.google.firebase.ai.type.ImagenRawImage
8+
import com.google.firebase.ai.type.PublicPreviewAPI
59
import com.google.firebase.ai.type.ResponseModality
610
import com.google.firebase.ai.type.Schema
711
import com.google.firebase.ai.type.Tool
812
import com.google.firebase.ai.type.content
913
import com.google.firebase.ai.type.generationConfig
14+
import com.google.firebase.ai.type.toImagenInlineImage
1015
import com.google.firebase.quickstart.ai.ui.navigation.Category
1116
import com.google.firebase.quickstart.ai.ui.navigation.Sample
1217

18+
@OptIn(PublicPreviewAPI::class)
1319
val FIREBASE_AI_SAMPLES = listOf(
1420
Sample(
1521
title = "Travel tips",
@@ -133,6 +139,23 @@ val FIREBASE_AI_SAMPLES = listOf(
133139
)
134140
}
135141
),
142+
Sample(
143+
title = "Imagen 3 - Inpainting",
144+
description = "Replace the background of an image using Imagen 3",
145+
modelName= "imagen-3.0-capability-001",
146+
backend = GenerativeBackend.vertexAI(),
147+
navRoute = "imagen",
148+
categories = listOf(Category.IMAGE),
149+
initialPrompt = content {
150+
text(
151+
"A sunny beach"
152+
)
153+
},
154+
includeAttach = true,
155+
bundleReferenceImages = {string: String, bitmap: Bitmap? ->
156+
listOf(ImagenRawImage(bitmap!!.toImagenInlineImage()), ImagenBackgroundMask())
157+
}
158+
),
136159
Sample(
137160
title = "Gemini 2.0 Flash - image generation",
138161
description = "Generate and/or edit images using Gemini 2.0 Flash",

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class MainActivity : ComponentActivity() {
8686
composable<ChatRoute> {
8787
ChatScreen()
8888
}
89-
// Imagen Samples
89+
// Imagn Samples
9090
composable<ImagenRoute> {
9191
ImagenScreen()
9292
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenScreen.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package com.google.firebase.quickstart.ai.feature.media.imagen
22

3+
import android.net.Uri
4+
import android.provider.OpenableColumns
5+
import android.text.format.Formatter
6+
import androidx.activity.compose.rememberLauncherForActivityResult
7+
import androidx.activity.result.contract.ActivityResultContracts
38
import androidx.compose.foundation.Image
49
import androidx.compose.foundation.layout.Box
510
import androidx.compose.foundation.layout.Column
@@ -24,6 +29,7 @@ import androidx.compose.runtime.setValue
2429
import androidx.compose.ui.Alignment
2530
import androidx.compose.ui.Modifier
2631
import androidx.compose.ui.graphics.asImageBitmap
32+
import androidx.compose.ui.platform.LocalContext
2733
import androidx.compose.ui.unit.dp
2834
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2935
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -40,6 +46,29 @@ fun ImagenScreen(
4046
val errorMessage by imagenViewModel.errorMessage.collectAsStateWithLifecycle()
4147
val isLoading by imagenViewModel.isLoading.collectAsStateWithLifecycle()
4248
val generatedImages by imagenViewModel.generatedBitmaps.collectAsStateWithLifecycle()
49+
val includeAttach by imagenViewModel.includeAttach.collectAsStateWithLifecycle()
50+
val context = LocalContext.current
51+
val contentResolver = context.contentResolver
52+
val openDocument = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { optionalUri: Uri? ->
53+
optionalUri?.let { uri ->
54+
var fileName: String? = null
55+
// Fetch file name and size
56+
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
57+
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
58+
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
59+
cursor.moveToFirst()
60+
val humanReadableSize = Formatter.formatShortFileSize(
61+
context, cursor.getLong(sizeIndex)
62+
)
63+
fileName = "${cursor.getString(nameIndex)} ($humanReadableSize)"
64+
}
65+
66+
contentResolver.openInputStream(uri)?.use { stream ->
67+
val bytes = stream.readBytes()
68+
imagenViewModel.attachImage(bytes, fileName)
69+
}
70+
}
71+
}
4372

4473
Column(
4574
modifier = Modifier
@@ -59,6 +88,18 @@ fun ImagenScreen(
5988
.padding(16.dp)
6089
.fillMaxWidth()
6190
)
91+
if (includeAttach) {
92+
TextButton(
93+
onClick = {
94+
openDocument.launch(arrayOf("image/*"))
95+
},
96+
modifier = Modifier
97+
.padding(end = 16.dp, bottom = 16.dp)
98+
.align(Alignment.End)
99+
100+
101+
) { Text("Attach") }
102+
}
62103
TextButton(
63104
onClick = {
64105
if (imagenPrompt.isNotBlank()) {

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/media/imagen/ImagenViewModel.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.google.firebase.quickstart.ai.feature.media.imagen
22

33
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
45
import androidx.lifecycle.SavedStateHandle
56
import androidx.lifecycle.ViewModel
67
import androidx.lifecycle.viewModelScope
@@ -10,6 +11,8 @@ import com.google.firebase.ai.ImagenModel
1011
import com.google.firebase.ai.ai
1112
import com.google.firebase.ai.type.GenerativeBackend
1213
import com.google.firebase.ai.type.ImagenAspectRatio
14+
import com.google.firebase.ai.type.ImagenEditMode
15+
import com.google.firebase.ai.type.ImagenEditingConfig
1316
import com.google.firebase.ai.type.ImagenImageFormat
1417
import com.google.firebase.ai.type.ImagenPersonFilterLevel
1518
import com.google.firebase.ai.type.ImagenSafetyFilterLevel
@@ -36,11 +39,15 @@ class ImagenViewModel(
3639
private val _isLoading = MutableStateFlow(false)
3740
val isLoading: StateFlow<Boolean> = _isLoading
3841

42+
private val _includeAttach = MutableStateFlow(sample.includeAttach)
43+
val includeAttach: StateFlow<Boolean> = _includeAttach
44+
3945
private val _generatedBitmaps = MutableStateFlow(listOf<Bitmap>())
4046
val generatedBitmaps: StateFlow<List<Bitmap>> = _generatedBitmaps
4147

4248
// Firebase AI Logic
4349
private val imagenModel: ImagenModel
50+
private var attachedImage: Bitmap?
4451

4552
init {
4653
val config = imagenGenerationConfig {
@@ -53,21 +60,31 @@ class ImagenViewModel(
5360
personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL
5461
)
5562
imagenModel = Firebase.ai(
56-
backend = GenerativeBackend.googleAI()
63+
backend = sample.backend
5764
).imagenModel(
5865
modelName = sample.modelName ?: "imagen-3.0-generate-002",
5966
generationConfig = config,
6067
safetySettings = settings
6168
)
69+
attachedImage = null
6270
}
6371

6472
fun generateImages(inputText: String) {
6573
viewModelScope.launch {
6674
_isLoading.value = true
6775
try {
68-
val imageResponse = imagenModel.generateImages(
69-
inputText
70-
)
76+
val bundleReferenceImages = sample.bundleReferenceImages
77+
val imageResponse = if (bundleReferenceImages == null) {
78+
imagenModel.generateImages(
79+
inputText
80+
)
81+
} else {
82+
imagenModel.editImage(
83+
bundleReferenceImages(inputText, attachedImage),
84+
inputText,
85+
ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
86+
)
87+
}
7188
_generatedBitmaps.value = imageResponse.images.map { it.asBitmap() }
7289
_errorMessage.value = null // clear error message
7390
} catch (e: Exception) {
@@ -77,4 +94,11 @@ class ImagenViewModel(
7794
}
7895
}
7996
}
97+
98+
fun attachImage(
99+
fileInBytes: ByteArray,
100+
fileName: String? = "Unnamed file"
101+
) {
102+
attachedImage = BitmapFactory.decodeByteArray(fileInBytes, 0, fileInBytes.size)
103+
}
80104
}

firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/ui/navigation/Sample.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.google.firebase.quickstart.ai.ui.navigation
22

3+
import android.graphics.Bitmap
34
import com.google.firebase.ai.type.Content
45
import com.google.firebase.ai.type.GenerationConfig
56
import com.google.firebase.ai.type.GenerativeBackend
7+
import com.google.firebase.ai.type.ImagenReferenceImage
8+
import com.google.firebase.ai.type.PublicPreviewAPI
69
import com.google.firebase.ai.type.Tool
710
import java.util.UUID
811

@@ -17,6 +20,7 @@ enum class Category(
1720
FUNCTION_CALLING("Function calling"),
1821
}
1922

23+
@OptIn(PublicPreviewAPI::class)
2024
data class Sample(
2125
val id: String = UUID.randomUUID().toString(), // used for navigation
2226
val title: String,
@@ -30,5 +34,7 @@ data class Sample(
3034
val systemInstructions: Content? = null,
3135
val generationConfig: GenerationConfig? = null,
3236
val chatHistory: List<Content> = emptyList(),
33-
val tools: List<Tool>? = null
37+
val tools: List<Tool>? = null,
38+
val includeAttach: Boolean = false,
39+
val bundleReferenceImages: ((String, Bitmap?) -> List<ImagenReferenceImage>)? = null
3440
)

0 commit comments

Comments
 (0)