Skip to content

Commit 7b1d972

Browse files
authored
InPainting - Mask & Edit Images using Gemini (#96)
1 parent f34c586 commit 7b1d972

File tree

17 files changed

+1218
-2
lines changed

17 files changed

+1218
-2
lines changed

ai-catalog/app/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ plugins {
2525

2626
android {
2727
namespace = "com.android.ai.catalog"
28-
compileSdk = 35
28+
compileSdk = 36
2929

3030
defaultConfig {
3131
applicationId = "com.android.ai.catalog"
3232
minSdk = 26
33-
targetSdk = 35
33+
targetSdk = 36
3434
versionCode = 1
3535
versionName = "1.0"
3636

@@ -86,6 +86,7 @@ dependencies {
8686
implementation(project(":samples:genai-image-description"))
8787
implementation(project(":samples:genai-writing-assistance"))
8888
implementation(project(":samples:imagen"))
89+
implementation(project(":samples:imagen-editing"))
8990
implementation(project(":samples:magic-selfie"))
9091
implementation(project(":samples:gemini-video-summarization"))
9192
implementation(project(":samples:gemini-live-todo"))

ai-catalog/app/src/main/java/com/android/ai/catalog/ui/domain/SampleCatalog.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.android.ai.samples.genai_image_description.GenAIImageDescriptionScree
2929
import com.android.ai.samples.genai_summarization.GenAISummarizationScreen
3030
import com.android.ai.samples.genai_writing_assistance.GenAIWritingAssistanceScreen
3131
import com.android.ai.samples.imagen.ui.ImagenScreen
32+
import com.android.ai.samples.imagenediting.ui.ImagenEditingScreen
3233
import com.android.ai.samples.magicselfie.ui.MagicSelfieScreen
3334

3435
@androidx.annotation.RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
@@ -86,6 +87,14 @@ val sampleCatalog = listOf(
8687
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE),
8788
needsFirebase = true,
8889
),
90+
SampleCatalogItem(
91+
title = R.string.imagen_editing_sample_title,
92+
description = R.string.imagen_editing_sample_description,
93+
route = "ImagenMaskEditing",
94+
sampleEntryScreen = { ImagenEditingScreen() },
95+
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE, SampleTags.MEDIA3),
96+
needsFirebase = true,
97+
),
8998
SampleCatalogItem(
9099
title = R.string.magic_selfie_sample_title,
91100
description = R.string.magic_selfie_sample_description,

ai-catalog/app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<string name="open_sample_button">Open sample</string>
1515
<string name="imagen_sample_title">Image generation with Imagen</string>
1616
<string name="imagen_sample_description">Generate images with Imagen, Google image generation model</string>
17+
<string name="imagen_editing_sample_title">Imagen Editing using Inpainting</string>
18+
<string name="imagen_editing_sample_description">Generate images and edit only specific areas of a generated image with Inpainting</string>
1719
<string name="magic_selfie_sample_title">Magic Selfie with Imagen and ML Kit</string>
1820
<string name="magic_selfie_sample_description">Change the background of your selfies with Imagen and the ML Kit Segmentation API</string>
1921
<string name="gemini_video_summarization_sample_title">Video Summarization with Gemini and Firebase</string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
plugins {
17+
alias(libs.plugins.android.library)
18+
alias(libs.plugins.jetbrains.kotlin.android)
19+
alias(libs.plugins.ksp)
20+
alias(libs.plugins.compose.compiler)
21+
}
22+
23+
android {
24+
namespace = "com.android.ai.samples.imagenediting"
25+
compileSdk = 36
26+
27+
buildFeatures {
28+
compose = true
29+
}
30+
31+
defaultConfig {
32+
minSdk = 24
33+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
34+
consumerProguardFiles("consumer-rules.pro")
35+
}
36+
37+
buildTypes {
38+
release {
39+
isMinifyEnabled = false
40+
proguardFiles(
41+
getDefaultProguardFile("proguard-android-optimize.txt"),
42+
"proguard-rules.pro",
43+
)
44+
}
45+
}
46+
47+
compileOptions {
48+
sourceCompatibility = JavaVersion.VERSION_17
49+
targetCompatibility = JavaVersion.VERSION_17
50+
}
51+
52+
kotlinOptions {
53+
jvmTarget = "17"
54+
}
55+
56+
lint {
57+
warningsAsErrors = true
58+
}
59+
}
60+
61+
dependencies {
62+
implementation(libs.androidx.core.ktx)
63+
implementation(libs.androidx.appcompat)
64+
implementation(libs.androidx.material3)
65+
implementation(platform(libs.androidx.compose.bom))
66+
implementation(libs.androidx.material.icons.extended)
67+
implementation(platform(libs.firebase.bom))
68+
implementation(libs.firebase.ai)
69+
implementation(libs.hilt.android)
70+
implementation(libs.hilt.navigation.compose)
71+
implementation(libs.androidx.runtime.livedata)
72+
implementation(libs.ui.tooling.preview)
73+
debugImplementation(libs.ui.tooling)
74+
ksp(libs.hilt.compiler)
75+
76+
testImplementation(libs.junit)
77+
androidTestImplementation(libs.androidx.junit)
78+
androidTestImplementation(libs.androidx.espresso.core)
79+
}

ai-catalog/samples/imagen-editing/consumer-rules.pro

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.android.ai.samples.imagenediting.data
17+
18+
import android.graphics.Bitmap
19+
import com.google.firebase.Firebase
20+
import com.google.firebase.ai.ai
21+
import com.google.firebase.ai.type.GenerativeBackend
22+
import com.google.firebase.ai.type.ImagenAspectRatio
23+
import com.google.firebase.ai.type.ImagenEditMode
24+
import com.google.firebase.ai.type.ImagenEditingConfig
25+
import com.google.firebase.ai.type.ImagenGenerationConfig
26+
import com.google.firebase.ai.type.ImagenImageFormat
27+
import com.google.firebase.ai.type.ImagenRawImage
28+
import com.google.firebase.ai.type.ImagenRawMask
29+
import com.google.firebase.ai.type.PublicPreviewAPI
30+
import com.google.firebase.ai.type.toImagenInlineImage
31+
import javax.inject.Inject
32+
import javax.inject.Singleton
33+
34+
/**
35+
* A data source that provides methods for interacting with the Firebase Imagen API
36+
* for various image generation and editing tasks.
37+
*
38+
* This class encapsulates the logic for initializing Imagen models and calling
39+
* their respective functions for image generation, inpainting, outpainting, and style transfer.
40+
* It leverages the Firebase AI SDK for seamless integration with Vertex AI backends.
41+
*
42+
* Note: This class uses `@OptIn(PublicPreviewAPI::class)` as Imagen features
43+
* are currently in public preview.
44+
*/
45+
@Singleton
46+
class ImagenEditingDataSource @Inject constructor() {
47+
private companion object {
48+
const val IMAGEN_MODEL_NAME = "imagen-4.0-ultra-generate-001"
49+
const val IMAGEN_EDITING_MODEL_NAME = "imagen-3.0-capability-001"
50+
const val DEFAULT_EDIT_STEPS = 50
51+
const val DEFAULT_STYLE_STRENGTH = 1
52+
}
53+
54+
@OptIn(PublicPreviewAPI::class)
55+
private val imagenModel =
56+
Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel(
57+
IMAGEN_MODEL_NAME,
58+
generationConfig = ImagenGenerationConfig(
59+
numberOfImages = 1,
60+
aspectRatio = ImagenAspectRatio.SQUARE_1x1,
61+
imageFormat = ImagenImageFormat.jpeg(compressionQuality = 75),
62+
),
63+
)
64+
65+
@OptIn(PublicPreviewAPI::class)
66+
private val editingModel =
67+
Firebase.ai(backend = GenerativeBackend.vertexAI()).imagenModel(
68+
IMAGEN_EDITING_MODEL_NAME,
69+
generationConfig = ImagenGenerationConfig(
70+
numberOfImages = 1,
71+
aspectRatio = ImagenAspectRatio.SQUARE_1x1,
72+
imageFormat = ImagenImageFormat.jpeg(compressionQuality = 75),
73+
),
74+
)
75+
76+
/**
77+
* Generates an image based on the provided prompt.
78+
*
79+
* This function uses the Imagen model to generate an image from a textual description.
80+
* It returns the generated image as a Bitmap.
81+
*
82+
* @param prompt The textual description to generate the image from.
83+
* @return The generated image as a [Bitmap].
84+
* @throws Exception if the image generation fails.
85+
*/
86+
@OptIn(PublicPreviewAPI::class)
87+
suspend fun generateImage(prompt: String): Bitmap {
88+
val imageResponse = imagenModel.generateImages(
89+
prompt = prompt,
90+
)
91+
val image = imageResponse.images.first()
92+
return image.asBitmap()
93+
}
94+
95+
/**
96+
* Performs inpainting on a source image using a provided mask and prompt.
97+
*
98+
* This function utilizes the Imagen editing model to fill in the masked areas
99+
* of the source image based on the textual prompt.
100+
*
101+
* @param sourceImage The original image to be inpainted.
102+
* @param maskImage A bitmap representing the mask, where white areas indicate
103+
* regions to be inpainted and black areas indicate regions to be preserved.
104+
* @param prompt A textual description of what should be generated in the masked areas.
105+
* @param editSteps The number of editing steps to perform. Defaults to `DEFAULT_EDIT_STEPS`.
106+
* @return A [Bitmap] representing the inpainted image.
107+
*/
108+
@OptIn(PublicPreviewAPI::class)
109+
suspend fun inpaintImageWithMask(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = DEFAULT_EDIT_STEPS): Bitmap {
110+
val imageResponse = editingModel.editImage(
111+
referenceImages = listOf(
112+
ImagenRawImage(sourceImage.toImagenInlineImage()),
113+
ImagenRawMask(maskImage.toImagenInlineImage()),
114+
),
115+
prompt = prompt,
116+
config = ImagenEditingConfig(
117+
editMode = ImagenEditMode.INPAINT_INSERTION,
118+
editSteps = editSteps,
119+
),
120+
)
121+
return imageResponse.images.first().asBitmap()
122+
}
123+
}

0 commit comments

Comments
 (0)