From 4bc6e099fa9a71cef2e90b93a7939ac9342798c3 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 15:55:53 +0100 Subject: [PATCH 1/8] Refactor image generation prompt This commit refactors the prompt used for image generation with edits. The new prompt focuses on adding a background to the Android bot image while ensuring the bot remains the main subject, prominently featured in the foreground and center of the frame. It also specifies that the bot should not be altered in shape or color and no new visual elements like hands, eyes, or mouths should be added. The following changes were made: - Renamed `generateImageWithEdit` to `addBackgroundToBot` in `ImageGenerationRepository` and `FirebaseAiDataSource` to better reflect its purpose. - Updated the prompt in `ImageGenerationRepositoryImpl` to include detailed instructions for background addition and bot preservation. - Updated `CustomizeExportViewModel` to use the new `addBackgroundToBot` method and the updated prompt. --- .../vertexai/FirebaseAiDataSource.kt | 2 +- .../data/ImageGenerationRepository.kt | 33 +++++++++++++++++-- .../customize/CustomizeExportViewModel.kt | 5 ++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt b/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt index fef0f431..897853c4 100644 --- a/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt +++ b/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt @@ -248,7 +248,7 @@ class FirebaseAiDataSourceImpl @Inject constructor( override suspend fun generateImageWithEdit( image: Bitmap, - backgroundPrompt: String, + backgroundPrompt: String ): Bitmap { val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel( modelName = remoteConfigDataSource.getImageGenerationEditsModelName(), diff --git a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt index 710f5d9e..9d0370d1 100644 --- a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt +++ b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt @@ -35,7 +35,8 @@ interface ImageGenerationRepository { suspend fun saveImage(imageBitmap: Bitmap): Uri suspend fun saveImageToExternalStorage(imageBitmap: Bitmap): Uri suspend fun saveImageToExternalStorage(imageUri: Uri): Uri - suspend fun generateImageWithEdit(image: Bitmap, editPrompt: String): Bitmap + + suspend fun addBackgroundToBot(image: Bitmap, backgroundPrompt: String) : Bitmap } @Singleton @@ -126,7 +127,33 @@ internal class ImageGenerationRepositoryImpl @Inject constructor( } } - override suspend fun generateImageWithEdit(image: Bitmap, editPrompt: String): Bitmap { - return firebaseAiDataSource.generateImageWithEdit(image, editPrompt) + override suspend fun addBackgroundToBot(image: Bitmap, backgroundPrompt: String): Bitmap { + /* val systemPrompt = """ + Always include the input Android Bot in the final result image as the subject of the image. + It should be prominently featured in the foreground, center of the frame, without any adjustments other + than the lighting of the surrounding environment. There should only be one of the bots in the image. + style="3d animation style, simplified shapes, mouthless character, realistic physics simulation" + + negativePrompt="fading, smile face, smile, mouth shape, mouth, teeth, lips, thumbs, + cartoony or overly bright visuals, smiling, new visual elements, hands, mouth, eyes, + paws, human-like visual elements, text, logos, dark, gritty, changing the character's color scheme, + sudden movements, glitching" + + """.trimIndent()*/ + val backgroundBotInstructions = """ + Add the input image android bot as the main subject to the result, + it should be the most prominent element of the resultant image, large and + filling the foreground, standing in the center of the frame with the central + focus, and the background just underneath the content. + + Always include the input Android Bot in the final result image as the subject of the image. + It should be prominently featured in the foreground, center of the frame, without any adjustments other + than the lighting of the surrounding environment. There should only be one of the bots in the image. + style="3d animation style, simplified shapes, mouthless character, realistic physics simulation" + + Do not alter the input Android Bot image, do not change its shape or add any hands, eyes, mouths etc. Do not change the characters color scheme. + + The background is described as follows: \"""".trimIndent() + backgroundPrompt + "\"" + return firebaseAiDataSource.generateImageWithEdit(image, backgroundBotInstructions) } } diff --git a/feature/results/src/main/java/com/android/developers/androidify/customize/CustomizeExportViewModel.kt b/feature/results/src/main/java/com/android/developers/androidify/customize/CustomizeExportViewModel.kt index d136ab11..64c95b78 100644 --- a/feature/results/src/main/java/com/android/developers/androidify/customize/CustomizeExportViewModel.kt +++ b/feature/results/src/main/java/com/android/developers/androidify/customize/CustomizeExportViewModel.kt @@ -147,9 +147,8 @@ class CustomizeExportViewModel @Inject constructor( _state.update { it.copy(showImageEditProgress = true) } try { - val bitmap = imageGenerationRepository.generateImageWithEdit( - image, - "Add the input image android bot as the main subject to the result, it should be the most prominent element of the resultant image, large and filling the foreground, standing in the center of the frame with the central focus, and the background just underneath the content. The background is described as follows: \"" + backgroundOption.prompt + "\"", + val bitmap = imageGenerationRepository.addBackgroundToBot( + image, backgroundOption.prompt, ) _state.update { it.copy( From 56de0d3246f80e1f0089afe2073f840ee2444900 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:16:57 +0100 Subject: [PATCH 2/8] Remove clip --- .../developers/androidify/customize/ImageRenderer.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt b/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt index 63522b46..706dcc0d 100644 --- a/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt +++ b/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt @@ -158,15 +158,9 @@ fun BackgroundLayout( .then(safeAnimateBounds) .rotate(rotationAnimation), ) { - val clip = if (exportImageCanvas.selectedBackgroundOption == BackgroundOption.None) { - Modifier - } else { - Modifier.clip(RoundedCornerShape(6)) - } Box( modifier = Modifier - .fillMaxSize() - .then(clip), + .fillMaxSize(), contentAlignment = Alignment.Center, ) { content() From 25f9354785649e5e34fb136d4da7e796018fc593 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:21:27 +0100 Subject: [PATCH 3/8] Add more background generation instructions. --- .../androidify/data/ImageGenerationRepository.kt | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt index 9d0370d1..43673ca3 100644 --- a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt +++ b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt @@ -128,22 +128,11 @@ internal class ImageGenerationRepositoryImpl @Inject constructor( } override suspend fun addBackgroundToBot(image: Bitmap, backgroundPrompt: String): Bitmap { - /* val systemPrompt = """ - Always include the input Android Bot in the final result image as the subject of the image. - It should be prominently featured in the foreground, center of the frame, without any adjustments other - than the lighting of the surrounding environment. There should only be one of the bots in the image. - style="3d animation style, simplified shapes, mouthless character, realistic physics simulation" - - negativePrompt="fading, smile face, smile, mouth shape, mouth, teeth, lips, thumbs, - cartoony or overly bright visuals, smiling, new visual elements, hands, mouth, eyes, - paws, human-like visual elements, text, logos, dark, gritty, changing the character's color scheme, - sudden movements, glitching" - - """.trimIndent()*/ val backgroundBotInstructions = """ Add the input image android bot as the main subject to the result, it should be the most prominent element of the resultant image, large and - filling the foreground, standing in the center of the frame with the central + filling the foreground - more than 50% of the resultant frame, + standing in the center of the frame with the central focus, and the background just underneath the content. Always include the input Android Bot in the final result image as the subject of the image. From cd705ee1ce7b88ac770c8ff759379f781e53b312 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:23:47 +0100 Subject: [PATCH 4/8] Only add the clip when its not an AI Background --- .../developers/androidify/customize/ImageRenderer.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt b/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt index 706dcc0d..3f023fb5 100644 --- a/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt +++ b/feature/results/src/main/java/com/android/developers/androidify/customize/ImageRenderer.kt @@ -158,9 +158,16 @@ fun BackgroundLayout( .then(safeAnimateBounds) .rotate(rotationAnimation), ) { + val clip = if (exportImageCanvas.selectedBackgroundOption == BackgroundOption.None + || exportImageCanvas.selectedBackgroundOption.aiBackground) { + Modifier + } else { + Modifier.clip(RoundedCornerShape(6)) + } Box( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .then(clip), contentAlignment = Alignment.Center, ) { content() From b1b8476e9ff9a4dc71aaa874648106a825a7c9d6 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:30:59 +0100 Subject: [PATCH 5/8] Fetch instructions from Firebase --- .../androidify/RemoteConfigDataSource.kt | 6 +++++ .../main/res/xml/remote_config_defaults.xml | 23 ++++++++++++++++--- .../data/ImageGenerationRepository.kt | 19 ++++----------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/core/network/src/main/java/com/android/developers/androidify/RemoteConfigDataSource.kt b/core/network/src/main/java/com/android/developers/androidify/RemoteConfigDataSource.kt index 0999fc9e..f8a38caf 100644 --- a/core/network/src/main/java/com/android/developers/androidify/RemoteConfigDataSource.kt +++ b/core/network/src/main/java/com/android/developers/androidify/RemoteConfigDataSource.kt @@ -40,6 +40,8 @@ interface RemoteConfigDataSource { fun getFineTunedModelName(): String fun getImageGenerationEditsModelName(): String + + fun getBotBackgroundInstructionPrompt(): String } @Singleton @@ -100,4 +102,8 @@ class RemoteConfigDataSourceImpl @Inject constructor() : RemoteConfigDataSource override fun getImageGenerationEditsModelName(): String { return remoteConfig.getString("image_generation_model_edits") } + + override fun getBotBackgroundInstructionPrompt(): String { + return remoteConfig.getString("bot_background_instruction_prompt") + } } diff --git a/core/network/src/main/res/xml/remote_config_defaults.xml b/core/network/src/main/res/xml/remote_config_defaults.xml index 36cfa63b..2d4dbb0c 100644 --- a/core/network/src/main/res/xml/remote_config_defaults.xml +++ b/core/network/src/main/res/xml/remote_config_defaults.xml @@ -16,12 +16,25 @@ --> - image_generation_model_edits - gemini-2.0-flash-preview-image-generation + bot_background_instruction_prompt + Add the input image android bot as the main subject to the result, + it should be the most prominent element of the resultant image, large and + filling the foreground - more than 50% of the resultant frame, + standing in the center of the frame with the central + focus, and the background just underneath the content. + + Always include the input Android Bot in the final result image as the subject of the image. + It should be prominently featured in the foreground, center of the frame, without any adjustments other + than the lighting of the surrounding environment. There should only be one of the bots in the image. + style="3d animation style, simplified shapes, mouthless character, realistic physics simulation" + + Do not alter the input Android Bot image, do not change its shape or add any hands, eyes, mouths etc. Do not change the characters color scheme. + + The background is described as follows: use_imagen - true + false prompt_image_validation @@ -127,6 +140,10 @@ promo_video_link https://services.google.com/fh/files/misc/androidfy_storyboard_b_v07.mp4 + + image_generation_model_edits + gemini-2.0-flash-preview-image-generation + text_model_name gemini-2.5-flash diff --git a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt index 43673ca3..9c3c881c 100644 --- a/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt +++ b/data/src/main/java/com/android/developers/androidify/data/ImageGenerationRepository.kt @@ -19,6 +19,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.util.Log +import com.android.developers.androidify.RemoteConfigDataSource import com.android.developers.androidify.model.ValidatedDescription import com.android.developers.androidify.model.ValidatedImage import com.android.developers.androidify.util.LocalFileProvider @@ -45,6 +46,7 @@ internal class ImageGenerationRepositoryImpl @Inject constructor( private val internetConnectivityManager: InternetConnectivityManager, private val geminiNanoDataSource: GeminiNanoGenerationDataSource, private val firebaseAiDataSource: FirebaseAiDataSource, + private val remoteConfigDataSource: RemoteConfigDataSource ) : ImageGenerationRepository { override suspend fun initialize() { @@ -128,21 +130,8 @@ internal class ImageGenerationRepositoryImpl @Inject constructor( } override suspend fun addBackgroundToBot(image: Bitmap, backgroundPrompt: String): Bitmap { - val backgroundBotInstructions = """ - Add the input image android bot as the main subject to the result, - it should be the most prominent element of the resultant image, large and - filling the foreground - more than 50% of the resultant frame, - standing in the center of the frame with the central - focus, and the background just underneath the content. - - Always include the input Android Bot in the final result image as the subject of the image. - It should be prominently featured in the foreground, center of the frame, without any adjustments other - than the lighting of the surrounding environment. There should only be one of the bots in the image. - style="3d animation style, simplified shapes, mouthless character, realistic physics simulation" - - Do not alter the input Android Bot image, do not change its shape or add any hands, eyes, mouths etc. Do not change the characters color scheme. - - The background is described as follows: \"""".trimIndent() + backgroundPrompt + "\"" + val backgroundBotInstructions = remoteConfigDataSource.getBotBackgroundInstructionPrompt() + + "\"" + backgroundPrompt + "\"" return firebaseAiDataSource.generateImageWithEdit(image, backgroundBotInstructions) } } From 1d16d204aca7fb4bb90d5a628c930c0f8294e9b5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:33:50 +0100 Subject: [PATCH 6/8] Test config --- .../developers/testing/network/TestRemoteConfigDataSource.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/testing/src/main/java/com/android/developers/testing/network/TestRemoteConfigDataSource.kt b/core/testing/src/main/java/com/android/developers/testing/network/TestRemoteConfigDataSource.kt index 8790c38b..8f5379b6 100644 --- a/core/testing/src/main/java/com/android/developers/testing/network/TestRemoteConfigDataSource.kt +++ b/core/testing/src/main/java/com/android/developers/testing/network/TestRemoteConfigDataSource.kt @@ -71,4 +71,8 @@ class TestRemoteConfigDataSource(private val useGeminiNano: Boolean) : RemoteCon override fun getImageGenerationEditsModelName(): String { return "test_image_model" } + + override fun getBotBackgroundInstructionPrompt(): String { + return "bot_background_instruction_prompt" + } } From 51472416c308bcd0cd5d13defc0c27dab60a9607 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 11 Aug 2025 18:39:09 +0100 Subject: [PATCH 7/8] Test config --- .../androidify/vertexai/FirebaseAiDataSource.kt | 14 +++++++------- .../repository/FakeImageGenerationRepository.kt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt b/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt index 897853c4..20103ff6 100644 --- a/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt +++ b/core/network/src/main/java/com/android/developers/androidify/vertexai/FirebaseAiDataSource.kt @@ -85,12 +85,12 @@ class FirebaseAiDataSourceImpl @Inject constructor( return Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel( remoteConfigDataSource.imageModelName(), safetySettings = - ImagenSafetySettings( - safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, - // Uses `ALLOW_ADULT` filter since `ALLOW_ALL` requires a special approval - // See https://cloud.google.com/vertex-ai/generative-ai/docs/image/responsible-ai-imagen#person-face-gen - personFilterLevel = ImagenPersonFilterLevel.ALLOW_ADULT, - ), + ImagenSafetySettings( + safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE, + // Uses `ALLOW_ADULT` filter since `ALLOW_ALL` requires a special approval + // See https://cloud.google.com/vertex-ai/generative-ai/docs/image/responsible-ai-imagen#person-face-gen + personFilterLevel = ImagenPersonFilterLevel.ALLOW_ADULT, + ), ) } @@ -248,7 +248,7 @@ class FirebaseAiDataSourceImpl @Inject constructor( override suspend fun generateImageWithEdit( image: Bitmap, - backgroundPrompt: String + backgroundPrompt: String, ): Bitmap { val model = Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel( modelName = remoteConfigDataSource.getImageGenerationEditsModelName(), diff --git a/core/testing/src/main/java/com/android/developers/testing/repository/FakeImageGenerationRepository.kt b/core/testing/src/main/java/com/android/developers/testing/repository/FakeImageGenerationRepository.kt index ab654b84..7cefbb1e 100644 --- a/core/testing/src/main/java/com/android/developers/testing/repository/FakeImageGenerationRepository.kt +++ b/core/testing/src/main/java/com/android/developers/testing/repository/FakeImageGenerationRepository.kt @@ -58,9 +58,9 @@ class FakeImageGenerationRepository : ImageGenerationRepository { return imageUri } - override suspend fun generateImageWithEdit( + override suspend fun addBackgroundToBot( image: Bitmap, - editPrompt: String, + backgroundPrompt: String, ): Bitmap { return createBitmap(1, 1) } From c1f6843a87f75edf82a6125221ad352b2f7b2569 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 12 Aug 2025 21:14:12 +0100 Subject: [PATCH 8/8] PR Feedback --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index cfac5c60..d15851d1 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,11 @@ fontName="Roboto Flex" For Googlers, get this info from go/androidify-api-setup +## Availability +Due to the background vibe feature using +`gemini-2.0-flash-preview-image-generation`, its not currently supported in a number of countries in Europe, Middle East & Africa. +See [this](https://ai.google.dev/gemini-api/docs/models#gemini-2.0-flash-preview-image-generation) doc for more information. + ## Contributing See [Contributing](CONTRIBUTING.md).