Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Imagen Editing/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ plugins {
}

android {
namespace = "com.android.ai.catalog"
namespace = "com.android.ai.samples.imagenediting"
compileSdk = 36

defaultConfig {
applicationId = "com.android.ai.catalog"
applicationId = "com.android.ai.samples.imagenediting"
minSdk = 26
targetSdk = 36
versionCode = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog
package com.android.ai.samples.imagenediting

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog
package com.android.ai.samples.imagenediting

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import com.android.ai.catalog.ui.CatalogApp
import com.android.ai.samples.imagenediting.ui.CatalogApp
import com.android.ai.theme.AISampleCatalogTheme
import dagger.hilt.android.AndroidEntryPoint

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog.domain
package com.android.ai.samples.imagenediting.domain

import android.Manifest
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresPermission
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.android.ai.catalog.R
import com.android.ai.samples.imagenediting.ui.ImagenEditingScreen
import com.android.ai.samples.imagenediting.R
import com.android.ai.samples.imagenediting.sample.ui.ImagenEditingScreen
import com.android.ai.theme.extendedColorScheme

@RequiresPermission(Manifest.permission.RECORD_AUDIO)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog.ui
package com.android.ai.samples.imagenediting.ui

import android.content.Intent
import android.util.Log
Expand Down Expand Up @@ -58,8 +58,8 @@ import androidx.core.net.toUri
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.android.ai.catalog.R
import com.android.ai.catalog.domain.sampleCatalog
import com.android.ai.samples.imagenediting.R
import com.android.ai.samples.imagenediting.domain.sampleCatalog
import com.google.firebase.FirebaseApp
import kotlinx.serialization.Serializable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog.ui
package com.android.ai.samples.imagenediting.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
Expand All @@ -37,9 +37,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.ai.catalog.R
import com.android.ai.catalog.domain.SampleCatalogItem
import com.android.ai.catalog.domain.SampleTags
import com.android.ai.samples.imagenediting.R
import com.android.ai.samples.imagenediting.domain.SampleCatalogItem
import com.android.ai.samples.imagenediting.domain.SampleTags
import com.android.ai.theme.AISampleCatalogTheme
import com.android.ai.uicomponent.Tag

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.catalog.ui
package com.android.ai.samples.imagenediting.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
Expand All @@ -33,9 +33,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.ai.catalog.R
import com.android.ai.catalog.domain.SampleCatalogItem
import com.android.ai.catalog.domain.SampleTags
import com.android.ai.samples.imagenediting.R
import com.android.ai.samples.imagenediting.domain.SampleCatalogItem
import com.android.ai.samples.imagenediting.domain.SampleTags
import com.android.ai.theme.AISampleCatalogTheme
import com.android.ai.uicomponent.Tag

Expand Down
2 changes: 1 addition & 1 deletion Imagen Editing/samples/imagen-editing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ plugins {
}

android {
namespace = "com.android.ai.samples.imagenediting"
namespace = "com.android.ai.samples.imagenediting.sample"
compileSdk = 36

buildFeatures {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.samples.imagenediting.data
package com.android.ai.samples.imagenediting.sample.data

import android.graphics.Bitmap
import com.google.firebase.Firebase
Expand Down Expand Up @@ -46,18 +46,33 @@ import javax.inject.Singleton
@Singleton
class ImagenEditingDataSource @Inject constructor() {
private companion object {
// TODO #1 - Define constants for Imagen model names and default values.
const val IMAGEN_MODEL_NAME = ""
const val IMAGEN_EDITING_MODEL_NAME = ""
const val IMAGEN_MODEL_NAME = "imagen-4.0-ultra-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
}

// TODO #2 - Implement Firebase calls using Imagen models
// @OptIn(PublicPreviewAPI::class)
// private val imagenModel =
@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 =
@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.
Expand Down Expand Up @@ -93,8 +108,18 @@ class ImagenEditingDataSource @Inject constructor() {
*/
@OptIn(PublicPreviewAPI::class)
suspend fun inpaintImage(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = DEFAULT_EDIT_STEPS): Bitmap {
// TODO #3 - Implement data source for inpainting;
return sourceImage;
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()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using .first() is not safe as it will throw a NoSuchElementException if the images list is empty, causing the app to crash. The API might return an empty list even on a successful response. Using firstOrNull() and then handling the null case by throwing a more descriptive exception is a more robust approach.

Suggested change
return imageResponse.images.first().asBitmap()
return imageResponse.images.firstOrNull()?.asBitmap() ?: throw IllegalStateException("Imagen API returned no images for inpainting.")

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.samples.imagenediting.ui
package com.android.ai.samples.imagenediting.sample.ui

import android.graphics.Bitmap
import android.graphics.Paint
Expand Down Expand Up @@ -58,7 +58,7 @@ 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 com.android.ai.samples.imagenediting.sample.R
import kotlin.math.min

@Composable
Expand All @@ -80,12 +80,27 @@ fun ImagenEditingMaskEditor(sourceBitmap: Bitmap, onMaskFinalized: (Bitmap) -> U
.fillMaxWidth()
.pointerInput(Unit) {
detectDragGestures(
// TODO #4 - Implement Drag logic
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) }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Creating a new Path object on every drag event by calling Path().apply { addPath(it) } is inefficient and can lead to performance degradation and UI jank, as it allocates a new object on every move. This is a common workaround to trigger recomposition for mutable objects like Path that are not directly observable by Compose.

For better performance, consider a different state management strategy. For example, you could collect the Offsets in a mutableStateListOf during the drag gesture and then construct the Path once within the Canvas onDraw scope. This would avoid the rapid object allocation during the drag.

}
change.consume()
},
onDragEnd = {
currentPath?.let { paths.add(it) }
currentPath = null
},
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.samples.imagenediting.ui
package com.android.ai.samples.imagenediting.sample.ui

import android.graphics.Bitmap
import android.graphics.BitmapFactory
Expand Down Expand Up @@ -62,7 +62,7 @@ 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.samples.imagenediting.sample.R
import com.android.ai.uicomponent.GenerateButton
import com.android.ai.uicomponent.SampleDetailTopAppBar
import com.android.ai.uicomponent.TextInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.samples.imagenediting.ui
package com.android.ai.samples.imagenediting.sample.ui

import android.graphics.Bitmap

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ai.samples.imagenediting.ui
package com.android.ai.samples.imagenediting.sample.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 com.android.ai.samples.imagenediting.sample.data.ImagenEditingDataSource
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -54,8 +54,23 @@ class ImagenEditingViewModel @Inject constructor(private val imagenDataSource: I
}

fun inpaintImage(sourceImage: Bitmap, maskImage: Bitmap, prompt: String, editSteps: Int = 50) {
// TODO #5 - Implement ViewModel Logic for inpainting
}
_uiState.value = ImagenEditingUIState.Loading
viewModelScope.launch {
try {
val inpaintedBitmap = imagenDataSource.inpaintImage(
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")
}
Comment on lines +70 to +72

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The exception is caught and an error message is shown to the user, but the exception itself is not logged. This is often referred to as "swallowing an exception" and makes debugging failures very difficult because the stack trace and other details are lost. It's a best practice to always log the caught exception to aid in debugging.

            } catch (e: Exception) {
                Log.e("ImagenEditingViewModel", "An error occurred during inpainting", e)
                _uiState.value = ImagenEditingUIState.Error(e.localizedMessage ?: "An unknown error occurred during inpainting")
            }

} }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line has a formatting issue with multiple closing braces and incorrect indentation. This makes the code harder to read and maintain. It's better to have each closing brace on its own line with the correct indentation.

        }
    }


fun onImageMaskReady(originalBitmap: Bitmap, maskBitmap: Bitmap) {
val originalContentDescription = (_uiState.value as? ImagenEditingUIState.ImageGenerated)?.contentDescription ?: "Edited image"
Expand Down
Loading