1818
1919package com.example.snippets.ai
2020
21+ import android.graphics.Bitmap
22+ import android.graphics.Paint
23+ import androidx.compose.foundation.Canvas
24+ import androidx.compose.foundation.Image
25+ import androidx.compose.foundation.gestures.detectDragGestures
26+ import androidx.compose.foundation.layout.Box
27+ import androidx.compose.foundation.layout.Column
28+ import androidx.compose.foundation.layout.fillMaxSize
29+ import androidx.compose.foundation.layout.fillMaxWidth
30+ import androidx.compose.material3.Button
31+ import androidx.compose.material3.Text
32+ import androidx.compose.runtime.Composable
33+ import androidx.compose.runtime.getValue
34+ import androidx.compose.runtime.mutableFloatStateOf
35+ import androidx.compose.runtime.mutableStateListOf
36+ import androidx.compose.runtime.mutableStateOf
37+ import androidx.compose.runtime.remember
38+ import androidx.compose.runtime.setValue
39+ import androidx.compose.runtime.snapshots.SnapshotStateList
40+ import androidx.compose.ui.Modifier
41+ import androidx.compose.ui.geometry.Offset
42+ import androidx.compose.ui.graphics.Path
43+ import androidx.compose.ui.graphics.StrokeCap
44+ import androidx.compose.ui.graphics.StrokeJoin
45+ import androidx.compose.ui.graphics.asAndroidPath
46+ import androidx.compose.ui.graphics.asImageBitmap
47+ import androidx.compose.ui.graphics.drawscope.Stroke
48+ import androidx.compose.ui.graphics.drawscope.withTransform
49+ import androidx.compose.ui.input.pointer.pointerInput
50+ import androidx.compose.ui.layout.ContentScale
2151import com.google.firebase.Firebase
52+ import com.google.firebase.ai.ImagenModel
2253import com.google.firebase.ai.ai
54+ import com.google.firebase.ai.type.Dimensions
2355import com.google.firebase.ai.type.GenerativeBackend
2456import com.google.firebase.ai.type.ImagenAspectRatio
57+ import com.google.firebase.ai.type.ImagenBackgroundMask
58+ import com.google.firebase.ai.type.ImagenControlReference
59+ import com.google.firebase.ai.type.ImagenControlType
60+ import com.google.firebase.ai.type.ImagenEditingConfig
61+ import com.google.firebase.ai.type.ImagenEditMode
2562import com.google.firebase.ai.type.ImagenGenerationConfig
63+ import com.google.firebase.ai.type.ImagenGenerationResponse
2664import com.google.firebase.ai.type.ImagenImageFormat
65+ import com.google.firebase.ai.type.ImagenImagePlacement
66+ import com.google.firebase.ai.type.ImagenInlineImage
67+ import com.google.firebase.ai.type.ImagenMaskReference
2768import com.google.firebase.ai.type.ImagenPersonFilterLevel
69+ import com.google.firebase.ai.type.ImagenRawImage
2870import com.google.firebase.ai.type.ImagenSafetyFilterLevel
2971import com.google.firebase.ai.type.ImagenSafetySettings
72+ import com.google.firebase.ai.type.ImagenStyleReference
73+ import com.google.firebase.ai.type.ImagenSubjectReference
74+ import com.google.firebase.ai.type.ImagenSubjectReferenceType
3075import com.google.firebase.ai.type.PublicPreviewAPI
76+ import com.google.firebase.ai.type.toImagenInlineImage
3177import kotlinx.coroutines.CoroutineScope
3278import kotlinx.coroutines.launch
79+ import kotlin.math.min
80+ import android.graphics.Color as AndroidColor
81+ import androidx.compose.ui.graphics.Color as ComposeColor
3382
3483private object ImagenModelConfiguration {
3584 // [START android_imagen_model_configuration]
@@ -53,6 +102,13 @@ private object ImagenModelConfiguration {
53102 // [END android_imagen_model_configuration]
54103}
55104
105+ private object ImagenVertexAIModelConfiguration {
106+ // [START android_imagen_vertex_model_configuration]
107+ val imagenModel = Firebase .ai(backend = GenerativeBackend .vertexAI())
108+ .imagenModel(" imagen-3.0-capability-001" )
109+ // [END android_imagen_vertex_model_configuration]
110+ }
111+
56112private fun generateImagesWithImagen (scope : CoroutineScope ) {
57113 val model = ImagenModelConfiguration .model
58114 scope.launch {
@@ -65,3 +121,277 @@ private fun generateImagesWithImagen(scope: CoroutineScope) {
65121 // [END android_imagen_generate_images]
66122 }
67123}
124+
125+ // [START android_imagen_inpaint_insertion]
126+ suspend fun insertFlowersIntoImage (
127+ model : ImagenModel ,
128+ originalImage : Bitmap ,
129+ mask : ImagenMaskReference
130+ ): ImagenGenerationResponse <ImagenInlineImage > {
131+ val prompt = " a vase of flowers"
132+
133+ // Pass the original image, a mask, the prompt, and an editing configuration.
134+ val editedImage = model.editImage(
135+ referenceImages = listOf (
136+ ImagenRawImage (originalImage.toImagenInlineImage()),
137+ mask,
138+ ),
139+ prompt = prompt,
140+ // Define the editing configuration for inpainting and insertion.
141+ config = ImagenEditingConfig (ImagenEditMode .INPAINT_INSERTION )
142+ )
143+ return editedImage
144+ }
145+ // [END android_imagen_inpaint_insertion]
146+
147+ // [START android_imagen_inpaint_removal]
148+ suspend fun removeBallFromImage (
149+ model : ImagenModel ,
150+ originalImage : Bitmap ,
151+ mask : ImagenMaskReference
152+ ): ImagenGenerationResponse <ImagenInlineImage > {
153+
154+ // Optional: provide the prompt describing the content to be removed.
155+ val prompt = " a ball"
156+
157+ // Pass the original image, a mask, the prompt, and an editing configuration.
158+ val editedImage = model.editImage(
159+ referenceImages = listOf (
160+ ImagenRawImage (originalImage.toImagenInlineImage()),
161+ mask
162+ ),
163+ prompt = prompt,
164+ // Define the editing configuration for inpainting and removal.
165+ config = ImagenEditingConfig (ImagenEditMode .INPAINT_REMOVAL )
166+ )
167+
168+ return editedImage
169+ }
170+ // [END android_imagen_inpaint_removal]
171+
172+ // [START android_imagen_editing_mask_editor]
173+ @Composable
174+ fun ImagenEditingMaskEditor (
175+ sourceBitmap : Bitmap ,
176+ onMaskFinalized : (Bitmap ) -> Unit ,
177+ ) {
178+
179+ val paths = remember { mutableStateListOf<Path >() }
180+ var currentPath by remember { mutableStateOf<Path ?>(null ) }
181+ var scale by remember { mutableFloatStateOf(1f ) }
182+ var offsetX by remember { mutableFloatStateOf(0f ) }
183+ var offsetY by remember { mutableFloatStateOf(0f ) }
184+
185+ Column (
186+ modifier = Modifier .fillMaxSize(),
187+ ) {
188+ Box (
189+ modifier = Modifier
190+ .fillMaxWidth()
191+ .pointerInput(Unit ) {
192+ detectDragGestures(
193+ onDragStart = { startOffset ->
194+ val transformedStart = Offset (
195+ (startOffset.x - offsetX) / scale,
196+ (startOffset.y - offsetY) / scale,
197+ )
198+ currentPath = Path ().apply { moveTo(transformedStart.x, transformedStart.y) }
199+ },
200+ onDrag = { change, _ ->
201+ currentPath?.let {
202+ val transformedChange = Offset (
203+ (change.position.x - offsetX) / scale,
204+ (change.position.y - offsetY) / scale,
205+ )
206+ it.lineTo(transformedChange.x, transformedChange.y)
207+ currentPath = Path ().apply { addPath(it) }
208+ }
209+ change.consume()
210+ },
211+ onDragEnd = {
212+ currentPath?.let { paths.add(it) }
213+ currentPath = null
214+ },
215+ )
216+ },
217+ ) {
218+ Image (
219+ bitmap = sourceBitmap.asImageBitmap(),
220+ contentDescription = null ,
221+ modifier = Modifier .fillMaxSize(),
222+ contentScale = ContentScale .Fit ,
223+ )
224+ Canvas (modifier = Modifier .fillMaxSize()) {
225+ val canvasWidth = size.width
226+ val canvasHeight = size.height
227+ val bitmapWidth = sourceBitmap.width.toFloat()
228+ val bitmapHeight = sourceBitmap.height.toFloat()
229+ scale = min(canvasWidth / bitmapWidth, canvasHeight / bitmapHeight)
230+ offsetX = (canvasWidth - bitmapWidth * scale) / 2
231+ offsetY = (canvasHeight - bitmapHeight * scale) / 2
232+ withTransform(
233+ {
234+ translate(left = offsetX, top = offsetY)
235+ scale(scale, scale, pivot = Offset .Zero )
236+ },
237+ ) {
238+ val strokeWidth = 70f / scale
239+ val stroke = Stroke (width = strokeWidth, cap = StrokeCap .Round , join = StrokeJoin .Round )
240+ val pathColor = ComposeColor .White .copy(alpha = 0.5f )
241+ paths.forEach { path ->
242+ drawPath(path = path, color = pathColor, style = stroke)
243+ }
244+ currentPath?.let { path ->
245+ drawPath(path = path, color = pathColor, style = stroke)
246+ }
247+ }
248+ }
249+ }
250+ Button (
251+ onClick = {
252+ val maskBitmap = createMaskBitmap(sourceBitmap, paths)
253+ onMaskFinalized(maskBitmap)
254+ },
255+ ) {
256+ Text (" Save mask" )
257+ }
258+ }
259+ }
260+ // [END android_imagen_editing_mask_editor]
261+
262+ // [START android_imagen_editing_create_mask]
263+ private fun createMaskBitmap (
264+ sourceBitmap : Bitmap ,
265+ paths : SnapshotStateList <Path >,
266+ ): Bitmap {
267+ val maskBitmap = Bitmap .createBitmap(sourceBitmap.width, sourceBitmap.height, Bitmap .Config .ARGB_8888 )
268+ val canvas = android.graphics.Canvas (maskBitmap)
269+ val paint = Paint ().apply {
270+ color = AndroidColor .RED
271+ strokeWidth = 70f
272+ style = Paint .Style .STROKE
273+ strokeCap = Paint .Cap .ROUND
274+ strokeJoin = Paint .Join .ROUND
275+ isAntiAlias = true
276+ }
277+ paths.forEach { path -> canvas.drawPath(path.asAndroidPath(), paint) }
278+
279+ return maskBitmap
280+ }
281+ // [END android_imagen_editing_create_mask]
282+
283+ // [START android_imagen_expand_image]
284+ suspend fun expandImage (originalImage : Bitmap , imagenModel : ImagenModel ): ImagenGenerationResponse <ImagenInlineImage > {
285+
286+ // Optionally describe what should appear in the expanded area.
287+ val prompt = " a sprawling sandy beach next to the ocean"
288+
289+ val editedImage = imagenModel.outpaintImage(
290+ originalImage.toImagenInlineImage(),
291+ Dimensions (1024 , 1024 ),
292+ prompt = prompt,
293+ newPosition = ImagenImagePlacement .LEFT_CENTER
294+ )
295+
296+
297+ return editedImage
298+ }
299+ // [END android_imagen_expand_image]
300+
301+ // [START android_imagen_replace_background]
302+ suspend fun replaceBackground (model : ImagenModel , originalImage : Bitmap ): ImagenGenerationResponse <ImagenInlineImage > {
303+ // Provide the prompt describing the new background.
304+ val prompt = " space background"
305+
306+ // Pass the original image, a mask, the prompt, and an editing configuration.
307+ val editedImage = model.editImage(
308+ referenceImages = listOf (
309+ ImagenRawImage (originalImage.toImagenInlineImage()),
310+ ImagenBackgroundMask (),
311+ ),
312+ prompt = prompt,
313+ config = ImagenEditingConfig (ImagenEditMode .INPAINT_INSERTION )
314+ )
315+
316+ return editedImage
317+ }
318+ // [END android_imagen_replace_background]
319+
320+ // [START android_imagen_customize_subject]
321+ suspend fun customizeCatImage (model : ImagenModel , referenceCatImage : Bitmap ): ImagenGenerationResponse <ImagenInlineImage > {
322+
323+ // Define the subject reference using the reference image.
324+ val subjectReference = ImagenSubjectReference (
325+ image = referenceCatImage.toImagenInlineImage(),
326+ referenceId = 1 ,
327+ description = " cat" ,
328+ subjectType = ImagenSubjectReferenceType .ANIMAL
329+ )
330+
331+ // Provide a prompt that describes the final image.
332+ // The "[1]" links the prompt to the subject reference with ID 1.
333+ val prompt = " A cat[1] flying through outer space"
334+
335+ // Use the editImage API to perform the subject customization.
336+ val editedImage = model.editImage(
337+ referenceImages = listOf (subjectReference),
338+ prompt = prompt,
339+ config = ImagenEditingConfig (
340+ editSteps = 50 // Number of editing steps, a higher value can improve quality
341+ )
342+ )
343+
344+ return editedImage
345+ }
346+ // [END android_imagen_customize_subject]
347+
348+ // [START android_imagen_customize_control]
349+ suspend fun customizeCatImageByControl (model : ImagenModel , referenceImage : Bitmap ): ImagenGenerationResponse <ImagenInlineImage > {
350+
351+ // Define the subject reference using the reference image.
352+ val controlReference = ImagenControlReference (
353+ image = referenceImage.toImagenInlineImage(),
354+ referenceId = 1 ,
355+ type = ImagenControlType .SCRIBBLE ,
356+ )
357+
358+ val prompt = " A cat flying through outer space arranged like the scribble map[1]"
359+
360+ val editedImage = model.editImage(
361+ referenceImages = listOf (controlReference),
362+ prompt = prompt,
363+ config = ImagenEditingConfig (
364+ editSteps = 50
365+ ),
366+ )
367+
368+ return editedImage
369+ }
370+ // [END android_imagen_customize_control]
371+
372+ // [START android_imagen_customize_style]
373+ suspend fun customizeImageByStyle (model : ImagenModel , referenceVanGoghImage : Bitmap ): ImagenGenerationResponse <ImagenInlineImage > {
374+
375+ // Define the style reference using the reference image.
376+ val styleReference = ImagenStyleReference (
377+ image = referenceVanGoghImage.toImagenInlineImage(),
378+ referenceId = 1 ,
379+ description = " Van Gogh style"
380+ )
381+
382+ // Provide a prompt that describes the final image.
383+ // The "1" links the prompt to the style reference with ID 1.
384+ val prompt = " A cat flying through outer space, in the Van Gogh style[1]"
385+
386+ // Use the editImage API to perform the style customization.
387+ val editedImage = model.editImage(
388+ referenceImages = listOf (styleReference),
389+ prompt = prompt,
390+ config = ImagenEditingConfig (
391+ editSteps = 50 // Number of editing steps, a higher value can improve quality
392+ ),
393+ )
394+
395+ return editedImage
396+ }
397+ // [END android_imagen_customize_style]
0 commit comments