diff --git a/README.md b/README.md
index dbf33cb6..e0c035f2 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ Here is the list of samples you can find in the `/samples` folder:
| Samples | |
|:----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-|
| ✨📱☁️ **Hybrid Inference**:
A sample demonstrating a hybrid approach to generative AI, utilizing both on-device (Gemini Nano via ML Kit) and cloud-based (Gemini via Firebase AI SDK) models. It showcases how to fallback to the cloud when on-device capabilities are unavailable.
**[> Browse code](samples/gemini-hybrid)**
|
+|
| ✨📱☁️ **Hybrid Inference**:
A sample demonstrating a hybrid approach to generative AI, utilizing both on-device (Gemini Nano via ML Kit) and cloud-based (Gemini via Firebase AI SDK) models. It showcases how to fallback to the cloud when on-device capabilities are unavailable.
**[> Browse code](samples/gemini-hybrid)**
|
| | |
|
| ✨🖼️🍌 **Gemini Image Chat**:
A chatbot app using the new [Gemini 3 Pro Image model](https://deepmind.google/models/gemini-image/pro/) (a.k.a. "Nano Banana Pro") enabling image generation and iterations via conversation with the Gemini model. Ask the model to generate an image and ask for tweaks in the chat.
**[> Browse code](samples/gemini-image-chat)**
|
| | |
@@ -44,19 +44,17 @@ Here is the list of samples you can find in the `/samples` folder:
| | |
|
| ✨📱🔍 **On-device Image Description**:
A sample letting you generate image descriptions using Gemini Nano via the [GenAI Image Description API](https://developers.google.com/ml-kit/genai/image-description/android).
**[> Browse code](samples/genai-image-description)**
|
| | |
-|
| ✨📱🖋️ **On-device Writing Assistance**:
A sample letting you proofread and rewrite text using Gemini Nano via the [GenAI Rewriting API](https://developers.google.com/ml-kit/genai/rewriting/android).
**[> Browse code](samples/genai-writing-assistance)**
|
+|
| ✨📱🖋️ **On-device Writing Assistance**:
A sample letting you proofread and rewrite text using Gemini Nano via the [GenAI Rewriting API](https://developers.google.com/ml-kit/genai/rewriting/android).
**[> Browse code](samples/genai-writing-assistance)**
|
| | |
-|
| 🖼️ **Image Generation with Imagen**:
A sample using [Imagen to generate images](https://developer.android.com/ai/imagen#generate-image) of landscapes, objects and people in various artistic style.
**[> Browse code](samples/imagen)**
|
+|
| 🖼️🍌 **Nanobanana**:
A sample using [Gemini 3.1 Flash Image model](https://developer.android.com/ai/gemini) (a.k.a. \"Nano Banana\") to generate images of landscapes, objects and people in various artistic style.
**[> Browse code](samples/nanobanana)**
|
| | |
-|
| 🖼️📸 **Magic Selfie**:
A sample using [ML Kit subject Segmentation SDK](https://developers.google.com/ml-kit/vision/subject-segmentation/android) to remove the background behind a person, and [Imagen](https://developer.android.com/ai/imagen#generate-image) to generate new background.
**[> Browse code](samples/magic-selfie)**
|
+|
| 🖼️📸 **Magic Selfie**:
A sample using [ML Kit subject Segmentation SDK](https://developers.google.com/ml-kit/vision/subject-segmentation/android) to remove the background behind a person, and Nano Banana to generate new background.
**[> Browse code](samples/magic-selfie)**
|
| | |
|
| ✨🎥 **Gemini Video Summarization**:
A sample using Gemini Flash to [summarize videos](https://firebase.google.com/docs/ai-logic/analyze-video?api=dev) leveraging the [large file support](https://firebase.google.com/docs/ai-logic/solutions/cloud-storage).
**[> Browse code](samples/gemini-video-summarization)**
|
| | |
|
| ✨🎥 **Gemini Video Metadata Creation**:
A sample using Gemini Flash to generate thumbnails, descriptions, hashtags, account tags, chapters and links from a video. This sample leverages the ability to provide a [Youtube video link](https://firebase.google.com/docs/ai-logic/input-file-requirements?api=dev#provide-file-using-url) to the model context for inference.
**[> Browse code](samples/gemini-video-metadata-creation)**
|
| | |
-|
| ✨🗣️ **Gemini Live API To-do App**:
A to-do list app using the [Gemini Live API](https://developer.android.com/ai/gemini/live) to let the user interact with Gemini via voice to update the todo list.
**[> Browse code](samples/gemini-live-todo)**
|
-| | |
-|
| 🖼️🖌️ **Imagen Editing**:
A sample using Imagen to [generate images](https://developer.android.com/ai/imagen#generate-image) and [editing images](https://developer.android.com/ai/imagen#editing) using the mask based editing capabilities of the model.
**[> Browse code](samples/imagen-editing)**
|
+|
| ✨🗣️ **Gemini Live API To-do App**:
A to-do list app using the [Gemini Live API](https://developer.android.com/ai/gemini/live) to let the user interact with Gemini via voice to update the todo list.
**[> Browse code](samples/gemini-live-todo)**
|
## Reporting issues
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8cdfcf69..12329c99 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -86,8 +86,7 @@ dependencies {
implementation(project(":samples:genai-summarization"))
implementation(project(":samples:genai-image-description"))
implementation(project(":samples:genai-writing-assistance"))
- implementation(project(":samples:imagen"))
- implementation(project(":samples:imagen-editing"))
+ implementation(project(":samples:nanobanana"))
implementation(project(":samples:magic-selfie"))
implementation(project(":samples:gemini-video-summarization"))
implementation(project(":samples:gemini-live-todo"))
diff --git a/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt b/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
index 1fadf927..51b15282 100644
--- a/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
+++ b/app/src/main/java/com/android/ai/catalog/domain/SampleCatalog.kt
@@ -32,8 +32,7 @@ import com.android.ai.samples.genai_image_description.GenAIImageDescriptionScree
import com.android.ai.samples.genai_summarization.GenAISummarizationScreen
import com.android.ai.samples.genai_writing_assistance.GenAIWritingAssistanceScreen
import com.android.ai.samples.geminihybrid.GeminiHybridScreen
-import com.android.ai.samples.imagen.ui.ImagenScreen
-import com.android.ai.samples.imagenediting.ui.ImagenEditingScreen
+import com.android.ai.samples.nanobanana.ui.NanobananaScreen
import com.android.ai.samples.magicselfie.ui.MagicSelfieScreen
import com.android.ai.theme.extendedColorScheme
import com.google.firebase.ai.type.PublicPreviewAPI
@@ -61,15 +60,6 @@ val sampleCatalog = listOf(
needsFirebase = true,
isFeatured = true,
),
- SampleCatalogItem(
- title = R.string.imagen_editing_sample_list_title,
- description = R.string.imagen_editing_sample_list_description,
- route = "ImagenMaskEditing",
- sampleEntryScreen = { ImagenEditingScreen() },
- tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE),
- needsFirebase = true,
- keyArt = R.drawable.img_keyart_imagen,
- ),
SampleCatalogItem(
title = R.string.gemini_multimodal_sample_list_title,
description = R.string.gemini_multimodal_sample_list_description,
@@ -114,11 +104,11 @@ val sampleCatalog = listOf(
keyArt = R.drawable.img_keyart_text,
),
SampleCatalogItem(
- title = R.string.imagen_sample_list_title,
- description = R.string.imagen_sample_list_description,
- route = "ImagenImageGenerationScreen",
- sampleEntryScreen = { ImagenScreen() },
- tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE),
+ title = R.string.nanobanana_sample_list_title,
+ description = R.string.nanobanana_sample_list_description,
+ route = "NanobananaImageGenerationScreen",
+ sampleEntryScreen = { NanobananaScreen() },
+ tags = listOf(SampleTags.GEMINI_FLASH, SampleTags.FIREBASE),
needsFirebase = true,
keyArt = R.drawable.img_keyart_imagen,
),
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 19e449e6..f8876099 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,10 +13,8 @@
Android AI Samples
Android\nAI Samples
Open sample
- Image generation with Imagen
- Generate images with Imagen, Google image generation model
- Image Editing with Imagen
- Generate images and edit only specific areas of a generated image with inpainting
+ Image generation with Nanobanana
+ Generate images with Nanobanana, Google image generation model
Magic Selfie with Gemini
Change the background of your selfies with the Gemini Flash model
Video Summarization with Gemini and Firebase
diff --git a/samples/imagen-editing/.gitignore b/samples/imagen-editing/.gitignore
deleted file mode 100644
index 42afabfd..00000000
--- a/samples/imagen-editing/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/samples/imagen-editing/README.md b/samples/imagen-editing/README.md
deleted file mode 100644
index 04cf41b2..00000000
--- a/samples/imagen-editing/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Imagen Image Editing Sample
-
-This sample is part of the [AI Sample Catalog](../../). To build and run this sample, you should clone the entire repository.
-
-## Description
-
-This sample demonstrates how to edit images using the Imagen editing model. Users can generate an image, then draw a mask on it and provide a text prompt to inpaint (fill in) the masked area, showcasing advanced image manipulation capabilities with Imagen.
-
-
-

-
-
-## How it works
-
-The application uses the Firebase AI SDK (see [How to run](../../#how-to-run)) for Android to interact with Imagen. The core logic is in the [`ImagenEditingDataSource.kt`](https://github.com/android/ai-samples/blob/main/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/data/ImagenEditingDataSource.kt) file. It first generates a base image using the generation model. Then, for editing, it takes the source image, a user-drawn mask, and a text prompt, and sends them to the editing model's `editImage` method to perform inpainting.
-
-Here is the key snippet of code that performs inpainting from [`ImagenEditingDataSource.kt`](./src/main/java/com/android/ai/samples/imagenediting/data/ImagenEditingDataSource.kt):
-
-```kotlin
-@OptIn(PublicPreviewAPI::class)
-suspend fun inpaintImageWithMask(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = DEFAULT_EDIT_STEPS): Bitmap {
- val imageResponse = editingModel.editImage(
- referenceImages = listOf(
- ImagenRawImage(sourceImage.toImagenInlineImage()),
- ImagenRawMask(maskImage.toImagenInlineImage()),
- ),
- prompt = prompt,
- config = ImagenEditingConfig(
- editMode = ImagenEditMode.INPAINT_INSERTION,
- editSteps = editSteps,
- ),
- )
- return imageResponse.images.first().asBitmap()
-}
-```
-
-Read more about [Imagen](https://developer.android.com/ai/imagen) in the Android Documentation.
diff --git a/samples/imagen-editing/build.gradle.kts b/samples/imagen-editing/build.gradle.kts
deleted file mode 100644
index bf26d7b5..00000000
--- a/samples/imagen-editing/build.gradle.kts
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2025 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.
- */
-plugins {
- alias(libs.plugins.android.library)
- alias(libs.plugins.jetbrains.kotlin.android)
- alias(libs.plugins.ksp)
- alias(libs.plugins.compose.compiler)
-}
-
-android {
- namespace = "com.android.ai.samples.imagenediting"
- compileSdk = 36
-
- buildFeatures {
- compose = true
- }
-
- defaultConfig {
- minSdk = 24
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles("consumer-rules.pro")
- }
-
- buildTypes {
- release {
- isMinifyEnabled = false
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro",
- )
- }
- }
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-
- lint {
- warningsAsErrors = true
- }
-}
-
-dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.appcompat)
- implementation(libs.androidx.material3)
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.material.icons.extended)
- implementation(platform(libs.firebase.bom))
- implementation(libs.firebase.ai)
- implementation(libs.hilt.android)
- implementation(libs.hilt.navigation.compose)
- implementation(libs.androidx.runtime.livedata)
- implementation(libs.ui.tooling.preview)
- implementation(project(":ui-component"))
- debugImplementation(libs.ui.tooling)
- ksp(libs.hilt.compiler)
-
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.junit)
- androidTestImplementation(libs.androidx.espresso.core)
-}
diff --git a/samples/imagen-editing/imagen_editing.png b/samples/imagen-editing/imagen_editing.png
deleted file mode 100644
index d603efa2..00000000
Binary files a/samples/imagen-editing/imagen_editing.png and /dev/null differ
diff --git a/samples/imagen-editing/proguard-rules.pro b/samples/imagen-editing/proguard-rules.pro
deleted file mode 100644
index 481bb434..00000000
--- a/samples/imagen-editing/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/data/ImagenEditingDataSource.kt b/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/data/ImagenEditingDataSource.kt
deleted file mode 100644
index a5b03f39..00000000
--- a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/data/ImagenEditingDataSource.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagenediting.data
-
-import android.graphics.Bitmap
-import com.google.firebase.Firebase
-import com.google.firebase.ai.ai
-import com.google.firebase.ai.type.GenerativeBackend
-import com.google.firebase.ai.type.ImagenAspectRatio
-import com.google.firebase.ai.type.ImagenEditMode
-import com.google.firebase.ai.type.ImagenEditingConfig
-import com.google.firebase.ai.type.ImagenGenerationConfig
-import com.google.firebase.ai.type.ImagenImageFormat
-import com.google.firebase.ai.type.ImagenRawImage
-import com.google.firebase.ai.type.ImagenRawMask
-import com.google.firebase.ai.type.PublicPreviewAPI
-import com.google.firebase.ai.type.toImagenInlineImage
-import javax.inject.Inject
-import javax.inject.Singleton
-
-/**
- * A data source that provides methods for interacting with the Firebase Imagen API
- * for various image generation and editing tasks.
- *
- * This class encapsulates the logic for initializing Imagen models and calling
- * their respective functions for image generation, inpainting, outpainting, and style transfer.
- * It leverages the Firebase AI SDK for seamless integration with Vertex AI backends.
- *
- * Note: This class uses `@OptIn(PublicPreviewAPI::class)` as Imagen features
- * are currently in public preview.
- */
-@Singleton
-class ImagenEditingDataSource @Inject constructor() {
- private companion object {
- const val IMAGEN_MODEL_NAME = "imagen-4.0-generate-001"
- const val IMAGEN_EDITING_MODEL_NAME = "imagen-3.0-capability-001"
- const val DEFAULT_EDIT_STEPS = 50
- const val DEFAULT_STYLE_STRENGTH = 1
- }
-
- @OptIn(PublicPreviewAPI::class)
- private val imagenModel =
- Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel(
- IMAGEN_MODEL_NAME,
- generationConfig = ImagenGenerationConfig(
- numberOfImages = 1,
- aspectRatio = ImagenAspectRatio.SQUARE_1x1,
- imageFormat = ImagenImageFormat.jpeg(compressionQuality = 75),
- ),
- )
-
- @OptIn(PublicPreviewAPI::class)
- private val editingModel =
- Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel(
- IMAGEN_EDITING_MODEL_NAME,
- generationConfig = ImagenGenerationConfig(
- numberOfImages = 1,
- aspectRatio = ImagenAspectRatio.SQUARE_1x1,
- imageFormat = ImagenImageFormat.jpeg(compressionQuality = 75),
- ),
- )
-
- /**
- * Generates an image based on the provided prompt.
- *
- * This function uses the Imagen model to generate an image from a textual description.
- * It returns the generated image as a Bitmap.
- *
- * @param prompt The textual description to generate the image from.
- * @return The generated image as a [Bitmap].
- * @throws Exception if the image generation fails.
- */
- @OptIn(PublicPreviewAPI::class)
- suspend fun generateImage(prompt: String): Bitmap {
- val imageResponse = imagenModel.generateImages(
- prompt = prompt,
- )
- val image = imageResponse.images.first()
- return image.asBitmap()
- }
-
- /**
- * Performs inpainting on a source image using a provided mask and prompt.
- *
- * This function utilizes the Imagen editing model to fill in the masked areas
- * of the source image based on the textual prompt.
- *
- * @param sourceImage The original image to be inpainted.
- * @param maskImage A bitmap representing the mask, where white areas indicate
- * regions to be inpainted and black areas indicate regions to be preserved.
- * @param prompt A textual description of what should be generated in the masked areas.
- * @param editSteps The number of editing steps to perform. Defaults to `DEFAULT_EDIT_STEPS`.
- * @return A [Bitmap] representing the inpainted image.
- */
- @OptIn(PublicPreviewAPI::class)
- suspend fun inpaintImageWithMask(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = DEFAULT_EDIT_STEPS): Bitmap {
- val imageResponse = editingModel.editImage(
- referenceImages = listOf(
- ImagenRawImage(sourceImage.toImagenInlineImage()),
- ImagenRawMask(maskImage.toImagenInlineImage()),
- ),
- prompt = prompt,
- config = ImagenEditingConfig(
- editMode = ImagenEditMode.INPAINT_INSERTION,
- editSteps = editSteps,
- ),
- )
- return imageResponse.images.first().asBitmap()
- }
-}
diff --git a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingMaskEditor.kt b/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingMaskEditor.kt
deleted file mode 100644
index 6d66f321..00000000
--- a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingMaskEditor.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagenediting.ui
-
-import android.graphics.Bitmap
-import android.graphics.Paint
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.gestures.detectDragGestures
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.Undo
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Delete
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.StrokeJoin
-import androidx.compose.ui.graphics.asAndroidPath
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.graphics.drawscope.withTransform
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.createBitmap
-import com.android.ai.samples.imagenediting.R
-import kotlin.math.min
-
-@Composable
-fun ImagenEditingMaskEditor(sourceBitmap: Bitmap, onMaskFinalized: (Bitmap) -> Unit, onCancel: () -> Unit, modifier: Modifier = Modifier) {
- val paths = remember { mutableStateListOf() }
- var currentPath by remember { mutableStateOf(null) }
- var scale by remember { mutableFloatStateOf(1f) }
- var offsetX by remember { mutableFloatStateOf(0f) }
- var offsetY by remember { mutableFloatStateOf(0f) }
-
- Box(modifier = modifier) {
- Column(
- modifier = Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- Box(
- modifier = Modifier
- .weight(1f)
- .fillMaxWidth()
- .pointerInput(Unit) {
- detectDragGestures(
- onDragStart = { startOffset ->
- val transformedStart = Offset(
- (startOffset.x - offsetX) / scale,
- (startOffset.y - offsetY) / scale,
- )
- currentPath = Path().apply { moveTo(transformedStart.x, transformedStart.y) }
- },
- onDrag = { change, _ ->
- currentPath?.let {
- val transformedChange = Offset(
- (change.position.x - offsetX) / scale,
- (change.position.y - offsetY) / scale,
- )
- it.lineTo(transformedChange.x, transformedChange.y)
- currentPath = Path().apply { addPath(it) }
- }
- change.consume()
- },
- onDragEnd = {
- currentPath?.let { paths.add(it) }
- currentPath = null
- },
- )
- },
- ) {
- Image(
- bitmap = sourceBitmap.asImageBitmap(),
- contentDescription = stringResource(R.string.editing_image_to_mask),
- modifier = Modifier.fillMaxSize(),
- contentScale = ContentScale.Crop,
- )
- Canvas(modifier = Modifier.fillMaxSize()) {
- val canvasWidth = size.width
- val canvasHeight = size.height
- val bitmapWidth = sourceBitmap.width.toFloat()
- val bitmapHeight = sourceBitmap.height.toFloat()
- scale = min(canvasWidth / bitmapWidth, canvasHeight / bitmapHeight)
- offsetX = (canvasWidth - bitmapWidth * scale) / 2
- offsetY = (canvasHeight - bitmapHeight * scale) / 2
- withTransform(
- {
- translate(left = offsetX, top = offsetY)
- scale(scale, scale, pivot = Offset.Zero)
- },
- ) {
- val strokeWidth = 70f / scale
- val stroke = Stroke(width = strokeWidth, cap = StrokeCap.Round, join = StrokeJoin.Round)
- val pathColor = Color.White.copy(alpha = 0.5f)
- paths.forEach { path ->
- drawPath(path = path, color = pathColor, style = stroke)
- }
- currentPath?.let { path ->
- drawPath(path = path, color = pathColor, style = stroke)
- }
- }
- }
-
- Row(
- modifier = Modifier
- .padding(16.dp)
- .align(Alignment.BottomEnd)
- .background(color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(20.dp)),
- ) {
- Icon(
- Icons.Default.Delete,
- contentDescription = stringResource(R.string.cancel_masking),
- modifier = Modifier
- .padding(10.dp)
- .clickable(true) {
- onCancel()
- },
- )
- Icon(
- Icons.AutoMirrored.Filled.Undo,
- contentDescription = stringResource(R.string.undo_the_mask),
- modifier = Modifier
- .padding(10.dp)
- .clickable(true) {
- if (paths.isNotEmpty()) paths.removeAt(paths.lastIndex)
- },
- )
- Icon(
- Icons.Default.Check,
- contentDescription = stringResource(R.string.save_the_mask),
- modifier = Modifier
- .padding(10.dp)
- .clickable(true) {
- val maskBitmap = createBitmap(sourceBitmap.width, sourceBitmap.height)
- val canvas = android.graphics.Canvas(maskBitmap)
- val paint = Paint().apply {
- color = android.graphics.Color.WHITE
- strokeWidth = 70f
- style = Paint.Style.STROKE
- strokeCap = Paint.Cap.ROUND
- strokeJoin = Paint.Join.ROUND
- isAntiAlias = true
- }
- paths.forEach { path -> canvas.drawPath(path.asAndroidPath(), paint) }
- onMaskFinalized(maskBitmap)
- },
- )
- }
- }
- }
- }
-}
diff --git a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingScreen.kt b/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingScreen.kt
deleted file mode 100644
index 9699e7aa..00000000
--- a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingScreen.kt
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagenediting.ui
-
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.imePadding
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.widthIn
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.input.TextFieldState
-import androidx.compose.foundation.text.input.rememberTextFieldState
-import androidx.compose.material3.ContainedLoadingIndicator
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageShader
-import androidx.compose.ui.graphics.ShaderBrush
-import androidx.compose.ui.graphics.TileMode
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.SoftwareKeyboardController
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.ai.samples.imagenediting.R
-import com.android.ai.uicomponent.GenerateButton
-import com.android.ai.uicomponent.SampleDetailTopAppBar
-import com.android.ai.uicomponent.TextInput
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ImagenEditingScreen(viewModel: ImagenEditingViewModel = hiltViewModel()) {
- val uiState: ImagenEditingUIState by viewModel.uiState.collectAsStateWithLifecycle()
- val showMaskEditor: Boolean by viewModel.showMaskEditor.collectAsStateWithLifecycle()
- val bitmapForMasking: Bitmap? by viewModel.bitmapForMasking.collectAsStateWithLifecycle()
-
- ImagenEditingScreenContent(
- uiState = uiState,
- showMaskEditor = showMaskEditor,
- bitmapForMasking = bitmapForMasking,
- onGenerateClick = viewModel::generateImage,
- onInpaintClick = { source, mask, prompt -> viewModel.inpaintImage(source, mask, prompt) },
- onImageMaskReady = { source, mask -> viewModel.onImageMaskReady(source, mask) },
- onCancelMasking = viewModel::onCancelMasking,
- modifier = Modifier.fillMaxSize(),
- )
-}
-
-@Composable
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
-private fun ImagenEditingScreenContent(
- uiState: ImagenEditingUIState,
- showMaskEditor: Boolean,
- bitmapForMasking: Bitmap?,
- onGenerateClick: (String) -> Unit,
- onInpaintClick: (source: Bitmap, mask: Bitmap, prompt: String) -> Unit,
- onImageMaskReady: (source: Bitmap, mask: Bitmap) -> Unit,
- onCancelMasking: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val isGenerating = uiState is ImagenEditingUIState.Loading
- val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
-
- Scaffold(
- containerColor = MaterialTheme.colorScheme.surface,
- topBar = {
- SampleDetailTopAppBar(
- sampleName = stringResource(R.string.editing_title_image_generation_title),
- sampleDescription = stringResource(R.string.editing_title_image_generation_subtitle),
- sourceCodeUrl = "https://github.com/android/ai-samples/tree/main/samples/imagen-editing",
- onBackClick = { backDispatcher?.onBackPressed() },
- )
- },
- modifier = Modifier.fillMaxWidth(),
- ) { innerPadding ->
- val context = LocalContext.current
- val imageBitmap = remember {
- val bitmap = BitmapFactory.decodeResource(context.resources, com.android.ai.uicomponent.R.drawable.img_fill)
- bitmap.asImageBitmap()
- }
- val imageShader = remember {
- ImageShader(
- image = imageBitmap,
- tileModeX = TileMode.Repeated,
- tileModeY = TileMode.Repeated,
- )
- }
-
- Box(
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize(),
- contentAlignment = Alignment.Center,
- ) {
- Box(
- Modifier
- .padding(16.dp)
- .imePadding()
- .widthIn(max = 440.dp)
- .fillMaxHeight(0.85f)
- .border(
- 1.dp,
- MaterialTheme.colorScheme.outline,
- shape = RoundedCornerShape(40.dp),
- )
- .clip(RoundedCornerShape(40.dp))
- .background(ShaderBrush(imageShader)),
- contentAlignment = Alignment.Center,
- ) {
- val keyboardController = LocalSoftwareKeyboardController.current
-
- when (uiState) {
- is ImagenEditingUIState.Initial -> {
- Text(
- text = stringResource(R.string.generate_an_image_to_edit),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier
- .padding(24.dp)
- .align(Alignment.Center),
- )
-
- val textFieldState = rememberTextFieldState()
-
- TextField(
- textFieldState,
- isGenerating,
- onGenerateClick,
- keyboardController,
- placeholder = stringResource(R.string.describe_the_image_to_generate),
- )
- }
-
- is ImagenEditingUIState.Loading -> {
- Box(modifier.fillMaxSize()) {
- ContainedLoadingIndicator(
- modifier = Modifier
- .size(60.dp)
- .align(Alignment.Center),
- )
- }
- }
-
- is ImagenEditingUIState.ImageGenerated -> {
- if (showMaskEditor && bitmapForMasking != null) {
- val textFieldState = rememberTextFieldState()
-
- ImagenEditingMaskEditor(
- sourceBitmap = bitmapForMasking,
- onMaskFinalized = { maskBitmap ->
- onImageMaskReady(bitmapForMasking, maskBitmap)
- },
- onCancel = { onCancelMasking() },
- modifier = Modifier.fillMaxSize(),
- )
-
- Text(
- text = "Draw a mask on the image",
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier
- .padding(24.dp)
- .align(Alignment.TopCenter)
- .background(color = MaterialTheme.colorScheme.surfaceContainer),
- )
- } else {
- val textFieldState = rememberTextFieldState()
-
- Image(
- bitmap = uiState.bitmap.asImageBitmap(),
- contentDescription = uiState.contentDescription,
- contentScale = ContentScale.Crop,
- modifier = Modifier.fillMaxSize(),
- )
- TextField(
- textFieldState,
- isGenerating,
- onGenerateClick,
- keyboardController,
- placeholder = stringResource(R.string.describe_the_image_to_generate),
- )
- }
- }
-
- is ImagenEditingUIState.ImageMasked -> {
- Box(modifier = Modifier.fillMaxSize()) {
- Image(
- bitmap = uiState.originalBitmap.asImageBitmap(),
- contentDescription = stringResource(R.string.editing_generated_image),
- modifier = Modifier.fillMaxSize(),
- contentScale = ContentScale.Crop,
- )
- Image(
- bitmap = uiState.maskBitmap.asImageBitmap(),
- contentDescription = stringResource(R.string.editing_generated_mask),
- modifier = Modifier.fillMaxSize(),
- contentScale = ContentScale.Fit,
- colorFilter = ColorFilter.tint(Color.Red.copy(alpha = 0.5f)),
- )
- }
- val textFieldState = rememberTextFieldState()
-
- TextField(
- textFieldState = textFieldState,
- isGenerating = isGenerating,
- onGenerateClick = { prompt -> onInpaintClick(uiState.originalBitmap, uiState.maskBitmap, prompt) },
- keyboardController,
- placeholder = stringResource(R.string.describe_the_image_to_in_paint),
- )
- }
-
- else -> {}
- }
- }
- }
- }
-}
-
-@Composable
-private fun BoxScope.TextField(
- textFieldState: TextFieldState,
- isGenerating: Boolean,
- onGenerateClick: (String) -> Unit,
- keyboardController: SoftwareKeyboardController?,
- placeholder: String = "",
-) {
- TextInput(
- state = textFieldState,
- placeholder = placeholder,
- primaryButton = {
- GenerateButton(
- text = "",
- icon = painterResource(id = com.android.ai.uicomponent.R.drawable.ic_ai_img),
- modifier = Modifier
- .width(72.dp)
- .height(55.dp)
- .padding(4.dp),
- enabled = !isGenerating,
- onClick = {
- onGenerateClick(textFieldState.text.toString())
- keyboardController?.hide()
- },
- )
- },
- modifier = Modifier
- .widthIn(max = 646.dp)
- .padding(start = 10.dp, end = 10.dp, bottom = 10.dp)
- .align(Alignment.BottomCenter),
- )
-}
diff --git a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingUIState.kt b/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingUIState.kt
deleted file mode 100644
index 0a37a73b..00000000
--- a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingUIState.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagenediting.ui
-
-import android.graphics.Bitmap
-
-sealed interface ImagenEditingUIState {
- data object Initial : ImagenEditingUIState
- data object Loading : ImagenEditingUIState
- data class ImageGenerated(
- val bitmap: Bitmap,
- val contentDescription: String,
- ) : ImagenEditingUIState
-
- data class ImageMasked(
- val originalBitmap: Bitmap,
- val maskBitmap: Bitmap,
- val contentDescription: String,
- ) : ImagenEditingUIState
-
- data class Error(val message: String?) : ImagenEditingUIState
-}
diff --git a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingViewModel.kt b/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingViewModel.kt
deleted file mode 100644
index ce6f8620..00000000
--- a/samples/imagen-editing/src/main/java/com/android/ai/samples/imagenediting/ui/ImagenEditingViewModel.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagenediting.ui
-
-import android.graphics.Bitmap
-import android.util.Log
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.android.ai.samples.imagenediting.data.ImagenEditingDataSource
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-
-@HiltViewModel
-class ImagenEditingViewModel @Inject constructor(private val imagenDataSource: ImagenEditingDataSource) : ViewModel() {
-
- private val _uiState: MutableStateFlow = MutableStateFlow(ImagenEditingUIState.Initial)
- val uiState: StateFlow = _uiState
-
- private val _bitmapForMasking = MutableStateFlow(null)
- val bitmapForMasking: StateFlow = _bitmapForMasking
-
- private val _showMaskEditor = MutableStateFlow(false)
- val showMaskEditor: StateFlow = _showMaskEditor
-
- fun generateImage(prompt: String) {
- _uiState.value = ImagenEditingUIState.Loading
- viewModelScope.launch {
- try {
- val bitmap = imagenDataSource.generateImage(prompt)
-
- _bitmapForMasking.value = bitmap
- _showMaskEditor.value = true
- _uiState.value = ImagenEditingUIState.ImageGenerated(bitmap, contentDescription = prompt)
- } catch (e: Exception) {
- _uiState.value = ImagenEditingUIState.Error(e.message)
- }
- }
- }
-
- fun inpaintImage(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = 50) {
- _uiState.value = ImagenEditingUIState.Loading
- viewModelScope.launch {
- try {
- val inpaintedBitmap = imagenDataSource.inpaintImageWithMask(
- sourceImage = sourceImage,
- maskImage = maskImage,
- prompt = prompt,
- editSteps = editSteps,
- )
- _uiState.value = ImagenEditingUIState.ImageGenerated(
- bitmap = inpaintedBitmap,
- contentDescription = "Inpainted image based on prompt: $prompt",
- )
- } catch (e: Exception) {
- _uiState.value = ImagenEditingUIState.Error(e.localizedMessage ?: "An unknown error occurred during inpainting")
- }
- }
- }
-
- fun onImageMaskReady(originalBitmap: Bitmap, maskBitmap: Bitmap) {
- val originalContentDescription = (_uiState.value as? ImagenEditingUIState.ImageGenerated)?.contentDescription ?: "Edited image"
- _uiState.value = ImagenEditingUIState.ImageMasked(
- originalBitmap = originalBitmap,
- maskBitmap = maskBitmap,
- contentDescription = originalContentDescription,
- )
- _showMaskEditor.value = false
- _bitmapForMasking.value = null
- }
-
- fun onCancelMasking() {
- Log.d("ImagenEditingViewModel", "onCancelMasking")
- _showMaskEditor.value = false
- _bitmapForMasking.value = null
- _uiState.value = ImagenEditingUIState.Initial
- }
-}
diff --git a/samples/imagen-editing/src/main/res/values/strings.xml b/samples/imagen-editing/src/main/res/values/strings.xml
deleted file mode 100644
index 4f7efff1..00000000
--- a/samples/imagen-editing/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
- Generate
- Generating…
- Prompt
- Mask edit prompt
- Mask Edit
- Inpaint
- Enter a prompt and tap \"Generate\" to generate an image
- Edit Image
- Finalize Mask
- Generate an image, then tap to draw a mask.
- An image of dog working as a chef
- An unknown error occurred.
- Imagen Editing
- Generate images with Imagen, Google\'s image generation model.
- Image to be masked
- The generated image
- The generated mask
- Draw a mask
- Cancel masking
- Undo the mask
- Save the mask
- describe the image to generate
- Generate an image to edit
- describe the image to in-paint
-
\ No newline at end of file
diff --git a/samples/imagen/.gitignore b/samples/imagen/.gitignore
deleted file mode 100644
index 42afabfd..00000000
--- a/samples/imagen/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/samples/imagen/README.md b/samples/imagen/README.md
deleted file mode 100644
index 9adfa02e..00000000
--- a/samples/imagen/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Imagen Image Generation Sample
-
-This sample is part of the [AI Sample Catalog](../../). To build and run this sample, you should clone the entire repository.
-
-## Description
-
-This sample demonstrates how to generate images from text prompts using the Imagen model. Users can input a text description, and the generative model will create an image based on that prompt, showcasing the power of text-to-image generation with Imagen.
-
-
-

-
-
-## How it works
-
-The application uses the Firebase AI SDK (see [How to run](../../#how-to-run)) for Android to interact with Imagen. The core logic is in the [`ImagenDataSource.kt`](./src/main/java/com/android/ai/samples/imagen/data/ImagenDataSource.kt) file. An `imagenModel` is initialized with specific generation configurations (e.g., number of images, aspect ratio, image format). When a user provides a text prompt, it's passed to the `generateImages` method, which returns the generated image as a bitmap.
-
-Here is the key snippet of code that calls the generative model from [`ImagenDataSource.kt`](./src/main/java/com/android/ai/samples/imagen/data/ImagenDataSource.kt):
-
-```kotlin
-@OptIn(PublicPreviewAPI::class)
-suspend fun generateImage(prompt: String): Bitmap {
- val imageResponse = imagenModel.generateImages(
- prompt = prompt,
- )
- val image = imageResponse.images.first()
- return image.asBitmap()
-}
-```
-
-Read more about [Imagen](https://developer.android.com/ai/imagen) in the Android Documentation.
diff --git a/samples/imagen/imagen_image_generation.png b/samples/imagen/imagen_image_generation.png
deleted file mode 100644
index af4d3ad2..00000000
Binary files a/samples/imagen/imagen_image_generation.png and /dev/null differ
diff --git a/samples/imagen/proguard-rules.pro b/samples/imagen/proguard-rules.pro
deleted file mode 100644
index 481bb434..00000000
--- a/samples/imagen/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/data/ImagenDataSource.kt b/samples/imagen/src/main/java/com/android/ai/samples/imagen/data/ImagenDataSource.kt
deleted file mode 100644
index 6b42259a..00000000
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/data/ImagenDataSource.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagen.data
-
-import android.graphics.Bitmap
-import com.google.firebase.Firebase
-import com.google.firebase.ai.ai
-import com.google.firebase.ai.type.GenerativeBackend
-import com.google.firebase.ai.type.ImagenAspectRatio
-import com.google.firebase.ai.type.ImagenGenerationConfig
-import com.google.firebase.ai.type.ImagenImageFormat
-import com.google.firebase.ai.type.PublicPreviewAPI
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class ImagenDataSource @Inject constructor() {
- @OptIn(PublicPreviewAPI::class)
- private val imagenModel = Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel(
- modelName = "imagen-4.0-generate-001",
- generationConfig = ImagenGenerationConfig(
- numberOfImages = 1,
- aspectRatio = ImagenAspectRatio.SQUARE_1x1,
- imageFormat = ImagenImageFormat.jpeg(compressionQuality = 75),
- ),
- )
-
- @OptIn(PublicPreviewAPI::class)
- suspend fun generateImage(prompt: String): Bitmap {
- val imageResponse = imagenModel.generateImages(
- prompt = prompt,
- )
- val image = imageResponse.images.first()
- return image.asBitmap()
- }
-}
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GeneratedContent.kt b/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GeneratedContent.kt
deleted file mode 100644
index 7248170d..00000000
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GeneratedContent.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagen.ui
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material3.Card
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
-import com.android.ai.samples.imagen.R
-
-@Composable
-fun GeneratedContent(uiState: ImagenUIState, modifier: Modifier = Modifier) {
- Card(
- modifier = modifier,
- ) {
- when (uiState) {
- ImagenUIState.Initial -> {
- //
- }
-
- ImagenUIState.Loading -> {
- //
- }
-
- is ImagenUIState.ImageGenerated -> {
- Image(
- bitmap = uiState.bitmap.asImageBitmap(),
- contentDescription = uiState.contentDescription,
- contentScale = ContentScale.Fit,
- modifier = Modifier.fillMaxSize(),
- )
- }
-
- is ImagenUIState.Error -> {
- Text(
- text = uiState.message ?: stringResource(R.string.error_message_unknown),
- modifier = Modifier
- .fillMaxSize()
- .wrapContentSize(Alignment.Center),
- textAlign = TextAlign.Center,
- )
- }
- }
- }
-}
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GenerationInput.kt b/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GenerationInput.kt
deleted file mode 100644
index f124ed66..00000000
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/GenerationInput.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2025 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.android.ai.samples.imagen.ui
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.SmartToy
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.unit.dp
-import com.android.ai.samples.imagen.R
-
-@Composable
-fun GenerationInput(onGenerateClick: (String) -> Unit, enabled: Boolean, modifier: Modifier = Modifier) {
- val placeholder = stringResource(R.string.placeholder_prompt)
- var textFieldValue by rememberSaveable { mutableStateOf(placeholder) }
-
- Column(
- verticalArrangement = Arrangement.spacedBy(8.dp),
- modifier = modifier,
- ) {
- TextField(
- value = textFieldValue,
- onValueChange = { textFieldValue = it },
- label = { Text(stringResource(R.string.prompt_label)) },
- modifier = Modifier.fillMaxWidth(),
- enabled = enabled,
- keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send),
- keyboardActions = KeyboardActions(
- onSend = {
- onGenerateClick(textFieldValue)
- },
- ),
- )
- Button(
- onClick = {
- onGenerateClick(textFieldValue)
- },
- enabled = enabled,
- contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
- modifier = Modifier.fillMaxWidth(),
- ) {
- Icon(
- Icons.Default.SmartToy,
- contentDescription = null,
- modifier = Modifier.size(ButtonDefaults.IconSize),
- )
- Spacer(Modifier.size(ButtonDefaults.IconSpacing))
- Text(text = stringResource(R.string.generate_button))
- }
- }
-}
diff --git a/samples/nanobanana/.gitignore b/samples/nanobanana/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/samples/nanobanana/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/samples/nanobanana/README.md b/samples/nanobanana/README.md
new file mode 100644
index 00000000..dd4a4f20
--- /dev/null
+++ b/samples/nanobanana/README.md
@@ -0,0 +1,27 @@
+# Nanobanana Image Generation Sample
+
+This sample is part of the [AI Sample Catalog](../../). To build and run this sample, you should clone the entire repository.
+
+## Description
+
+This sample demonstrates how to generate images from text prompts using the Gemini 3.1 Flash Image model (a.k.a. "Nano Banana"). Users can input a text description, and the generative model will create an image based on that prompt, showcasing the power of text-to-image generation with Gemini.
+
+
+

+
+
+## How it works
+
+The application uses the Firebase AI SDK (see [How to run](../../#how-to-run)) for Android to interact with Gemini. The core logic is in the [`NanobananaDataSource.kt`](./src/main/java/com/android/ai/samples/nanobanana/data/NanobananaDataSource.kt) file. A `generativeModel` is initialized with specific configurations. When a user provides a text prompt, it's passed to the `generateImage` method, which returns the generated image as a bitmap.
+
+Here is the key snippet of code that calls the generative model from [`NanobananaDataSource.kt`](./src/main/java/com/android/ai/samples/nanobanana/data/NanobananaDataSource.kt):
+
+```kotlin
+suspend fun generateImage(prompt: String): Bitmap {
+ val response = generativeModel.generateContent(prompt)
+ return response.candidates.firstOrNull()?.content?.parts?.firstNotNullOfOrNull { it.asImageOrNull() }
+ ?: throw Exception("No image generated")
+}
+```
+
+Read more about [Gemini](https://developer.android.com/ai/gemini) in the Android Documentation.
diff --git a/samples/imagen/build.gradle.kts b/samples/nanobanana/build.gradle.kts
similarity index 97%
rename from samples/imagen/build.gradle.kts
rename to samples/nanobanana/build.gradle.kts
index b2ac122a..9a56b458 100644
--- a/samples/imagen/build.gradle.kts
+++ b/samples/nanobanana/build.gradle.kts
@@ -21,7 +21,7 @@ plugins {
}
android {
- namespace = "com.android.ai.samples.imagen"
+ namespace = "com.android.ai.samples.nanobanana"
compileSdk = 35
buildFeatures {
diff --git a/samples/imagen-editing/consumer-rules.pro b/samples/nanobanana/consumer-rules.pro
similarity index 100%
rename from samples/imagen-editing/consumer-rules.pro
rename to samples/nanobanana/consumer-rules.pro
diff --git a/samples/imagen/consumer-rules.pro b/samples/nanobanana/proguard-rules.pro
similarity index 100%
rename from samples/imagen/consumer-rules.pro
rename to samples/nanobanana/proguard-rules.pro
diff --git a/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/data/NanobananaDataSource.kt b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/data/NanobananaDataSource.kt
new file mode 100644
index 00000000..1408c6d3
--- /dev/null
+++ b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/data/NanobananaDataSource.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2025 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.android.ai.samples.nanobanana.data
+
+import android.graphics.Bitmap
+import com.google.firebase.Firebase
+import com.google.firebase.ai.ai
+import com.google.firebase.ai.type.GenerativeBackend
+import com.google.firebase.ai.type.ResponseModality
+import com.google.firebase.ai.type.asImageOrNull
+import com.google.firebase.ai.type.generationConfig
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class NanobananaDataSource @Inject constructor() {
+ private val generativeModel by lazy {
+ Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(
+ modelName = "gemini-3.1-flash-image-preview",
+ generationConfig = generationConfig {
+ responseModalities = listOf(ResponseModality.IMAGE)
+ }
+ )
+ }
+
+ suspend fun generateImage(prompt: String): Bitmap {
+ val response = generativeModel.generateContent(prompt)
+ return response.candidates.firstOrNull()?.content?.parts?.firstNotNullOfOrNull { it.asImageOrNull() }
+ ?: throw Exception("No image generated")
+ }
+}
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenScreen.kt b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaScreen.kt
similarity index 87%
rename from samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenScreen.kt
rename to samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaScreen.kt
index 10afed0e..89e3814b 100644
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenScreen.kt
+++ b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaScreen.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ai.samples.imagen.ui
+package com.android.ai.samples.nanobanana.ui
import android.graphics.BitmapFactory
import android.widget.Toast
@@ -57,7 +57,7 @@ import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.ai.samples.imagen.R
+import com.android.ai.samples.nanobanana.R
import com.android.ai.theme.AISampleCatalogTheme
import com.android.ai.uicomponent.GenerateButton
import com.android.ai.uicomponent.SampleDetailTopAppBar
@@ -65,14 +65,14 @@ import com.android.ai.uicomponent.TextInput
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
-fun ImagenScreen(viewModel: ImagenViewModel = hiltViewModel()) {
- val uiState: ImagenUIState by viewModel.uiState.collectAsStateWithLifecycle()
+fun NanobananaScreen(viewModel: NanobananaViewModel = hiltViewModel()) {
+ val uiState: NanobananaUIState by viewModel.uiState.collectAsStateWithLifecycle()
- if (uiState is ImagenUIState.Error) {
- Toast.makeText(LocalContext.current, (uiState as ImagenUIState.Error).message, Toast.LENGTH_SHORT).show()
+ if (uiState is NanobananaUIState.Error) {
+ Toast.makeText(LocalContext.current, (uiState as NanobananaUIState.Error).message, Toast.LENGTH_SHORT).show()
}
- ImagenScreen(
+ NanobananaScreen(
uiState = uiState,
onGenerateClick = viewModel::generateImage,
)
@@ -80,16 +80,16 @@ fun ImagenScreen(viewModel: ImagenViewModel = hiltViewModel()) {
@Composable
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
-private fun ImagenScreen(uiState: ImagenUIState, onGenerateClick: (String) -> Unit) {
- val isGenerating = uiState is ImagenUIState.Loading
+private fun NanobananaScreen(uiState: NanobananaUIState, onGenerateClick: (String) -> Unit) {
+ val isGenerating = uiState is NanobananaUIState.Loading
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
Scaffold(
containerColor = MaterialTheme.colorScheme.surface,
topBar = {
SampleDetailTopAppBar(
- sampleName = stringResource(R.string.title_image_generation_screen),
- sampleDescription = stringResource(R.string.subtitle_image_generation_screen),
- sourceCodeUrl = "https://github.com/android/ai-samples/tree/main/samples/imagen",
+ sampleName = stringResource(R.string.title_nanobanana_screen),
+ sampleDescription = stringResource(R.string.subtitle_nanobanana_screen),
+ sourceCodeUrl = "https://github.com/android/ai-samples/tree/main/samples/nanobanana",
onBackClick = { backDispatcher?.onBackPressed() },
)
},
@@ -133,13 +133,13 @@ private fun ImagenScreen(uiState: ImagenUIState, onGenerateClick: (String) -> Un
) {
when (uiState) {
- is ImagenUIState.ImageGenerated -> Image(
+ is NanobananaUIState.ImageGenerated -> Image(
bitmap = uiState.bitmap.asImageBitmap(),
contentDescription = uiState.contentDescription,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
- ImagenUIState.Loading -> {
+ NanobananaUIState.Loading -> {
ContainedLoadingIndicator(
modifier = Modifier.size(60.dp)
.align(Alignment.Center),
@@ -182,10 +182,10 @@ private fun ImagenScreen(uiState: ImagenUIState, onGenerateClick: (String) -> Un
@PreviewScreenSizes
@Composable
@OptIn(ExperimentalMaterial3Api::class)
-private fun ImagenScreenPreview() {
+private fun NanobananaScreenPreview() {
AISampleCatalogTheme {
- ImagenScreen(
- uiState = ImagenUIState.Initial,
+ NanobananaScreen(
+ uiState = NanobananaUIState.Initial,
onGenerateClick = {},
)
}
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenUIState.kt b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaUIState.kt
similarity index 74%
rename from samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenUIState.kt
rename to samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaUIState.kt
index 3bca1610..15be384a 100644
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenUIState.kt
+++ b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaUIState.kt
@@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ai.samples.imagen.ui
+package com.android.ai.samples.nanobanana.ui
import android.graphics.Bitmap
-sealed interface ImagenUIState {
- data object Initial : ImagenUIState
- data object Loading : ImagenUIState
+sealed interface NanobananaUIState {
+ data object Initial : NanobananaUIState
+ data object Loading : NanobananaUIState
data class ImageGenerated(
val bitmap: Bitmap,
val contentDescription: String,
- ) : ImagenUIState
- data class Error(val message: String?) : ImagenUIState
+ ) : NanobananaUIState
+ data class Error(val message: String?) : NanobananaUIState
}
diff --git a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenViewModel.kt b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaViewModel.kt
similarity index 60%
rename from samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenViewModel.kt
rename to samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaViewModel.kt
index b0f77d08..5e65ff05 100644
--- a/samples/imagen/src/main/java/com/android/ai/samples/imagen/ui/ImagenViewModel.kt
+++ b/samples/nanobanana/src/main/java/com/android/ai/samples/nanobanana/ui/NanobananaViewModel.kt
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ai.samples.imagen.ui
+package com.android.ai.samples.nanobanana.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.android.ai.samples.imagen.data.ImagenDataSource
+import com.android.ai.samples.nanobanana.data.NanobananaDataSource
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -25,20 +25,20 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@HiltViewModel
-class ImagenViewModel @Inject constructor(private val imagenDataSource: ImagenDataSource) : ViewModel() {
+class NanobananaViewModel @Inject constructor(private val nanobananaDataSource: NanobananaDataSource) : ViewModel() {
- private val _uiState: MutableStateFlow = MutableStateFlow(ImagenUIState.Initial)
- val uiState: StateFlow = _uiState
+ private val _uiState: MutableStateFlow = MutableStateFlow(NanobananaUIState.Initial)
+ val uiState: StateFlow = _uiState
fun generateImage(prompt: String) {
- _uiState.value = ImagenUIState.Loading
+ _uiState.value = NanobananaUIState.Loading
viewModelScope.launch {
try {
- val bitmap = imagenDataSource.generateImage(prompt)
- _uiState.value = ImagenUIState.ImageGenerated(bitmap, contentDescription = prompt)
+ val bitmap = nanobananaDataSource.generateImage(prompt)
+ _uiState.value = NanobananaUIState.ImageGenerated(bitmap, contentDescription = prompt)
} catch (e: Exception) {
- _uiState.value = ImagenUIState.Error(e.message)
+ _uiState.value = NanobananaUIState.Error(e.message)
}
}
}
diff --git a/samples/imagen/src/main/res/values/strings.xml b/samples/nanobanana/src/main/res/values/strings.xml
similarity index 55%
rename from samples/imagen/src/main/res/values/strings.xml
rename to samples/nanobanana/src/main/res/values/strings.xml
index 9edf57df..5f70ad9a 100644
--- a/samples/imagen/src/main/res/values/strings.xml
+++ b/samples/nanobanana/src/main/res/values/strings.xml
@@ -16,13 +16,8 @@
-->
- See Code
- An oil painting of Alcatraz
- Imagen image generation
- Generate images with Imagen, Google image generation model
- Generate
- Generating…
- Prompt
- Enter a prompt and tap \"Generate\" to generate an image
+ A yellow banana on a blue background
+ Nanobanana image generation
+ Generate images with Nanobanana, Google image generation model
Unknown error
-
\ No newline at end of file
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 74adde7f..14bffd8c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -43,12 +43,11 @@ include(":samples:gemini-chatbot")
include(":samples:genai-summarization")
include(":samples:genai-writing-assistance")
include(":samples:genai-image-description")
-include(":samples:imagen")
-include(":samples:imagen-editing")
include(":samples:magic-selfie")
include(":samples:gemini-video-summarization")
include(":samples:gemini-live-todo")
include(":samples:gemini-video-metadata-creation")
include(":samples:gemini-image-chat")
include(":samples:gemini-hybrid")
+include(":samples:nanobanana")
include(":ui-component")