Gerar imagens com o Imagen

O Imagen é um modelo de geração de imagens. Ela pode ser usada para gerar avatares personalizados para perfis de usuários ou integrar recursos visuais personalizados aos fluxos de tela atuais para aumentar o engajamento do usuário.

Você pode acessar os modelos do Imagen no seu app Android usando o SDK Firebase AI Logic. Os modelos do Imagen estão disponíveis usando os dois provedores de API do Firebase AI Logic: a API Gemini Developer (recomendada para a maioria dos desenvolvedores) e a Vertex AI.

Um diagrama que ilustra uma arquitetura de integração do Firebase AI Logic para acessar a API Gemini Developer. Um app Android usa o SDK do Firebase para Android para se conectar ao Firebase. Em seguida, o Firebase interage com a
       API Gemini Developer, que acessa o Gemini Pro e o Flash na
       nuvem.
Figura 1. Acesse os modelos do Imagen usando o Firebase AI Logic.

Teste os comandos

Criar os comandos ideais geralmente leva várias tentativas. Você pode testar comandos de imagem no Google AI Studio, um ambiente de desenvolvimento integrado para design e prototipagem de comandos. Para dicas sobre como melhorar seus comandos, consulte o guia de comandos e atributos de imagem.

Uma captura de tela da interface do Google AI Studio mostrando quatro imagens geradas de um T-Rex com uma mochila azul em uma floresta pré-histórica.
Figura 2. O Google AI Studio pode ajudar você a refinar seus comandos de geração de imagens.

Configurar um projeto do Firebase e conectar seu app

Siga as etapas na documentação do Firebase para adicionar o Firebase ao seu projeto Android.

Adicionar a dependência do Gradle

Adicione as seguintes dependências ao arquivo build.gradle:

dependencies {
  // Import the BoM for the Firebase platform
  implementation(platform("com.google.firebase:firebase-bom:34.4.0"))

  // Add the dependency for the Firebase AI Logic library. When using the BoM,
  // you don't specify versions in Firebase library dependencies
  implementation("com.google.firebase:firebase-ai")
}

Gerar uma imagem

Para gerar uma imagem no seu app Android, comece instanciando um ImagenModel com uma configuração opcional.

Use o parâmetro generationConfig para definir um comando negativo, o número de imagens, a proporção da imagem de saída, o formato da imagem e adicionar uma marca d'água. É possível usar o parâmetro safetySettings para configurar os filtros de segurança e de pessoas.

Kotlin

val config = ImagenGenerationConfig {
    numberOfImages = 2,
    aspectRatio = ImagenAspectRatio.LANDSCAPE_16x9,
    imageFormat = ImagenImageFormat.jpeg(compressionQuality = 100),
    addWatermark = false
}

// Initialize the Gemini Developer API backend service
// For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
val model = Firebase.ai(backend = GenerativeBackend.googleAI()).imagenModel(
    modelName = "imagen-4.0-generate-001",
    generationConfig = config,
    safetySettings = ImagenSafetySettings(
       safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
       personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL
    )
)

Java

ImagenGenerationConfig config = new ImagenGenerationConfig.Builder()
    .setNumberOfImages(2)
    .setAspectRatio(ImagenAspectRatio.LANDSCAPE_16x9)
    .setImageFormat(ImagenImageFormat.jpeg(100))
    .setAddWatermark(false)
    .build();

// For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
ImagenModelFutures model = ImagenModelFutures.from(
    FirebaseAI.ai(backend = GenerativeBackend.googleAI()).imagenModel(
       "imagen-4.0-generate-001",
       config,
       ImagenSafetySettings.builder()
          .setSafetyFilterLevel(ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE)
          .setPersonFilterLevel(ImagenPersonFilterLevel.BLOCK_ALL)
          .build())
);

Depois que o ImagenModel for instanciado, você poderá gerar imagens chamando generateImages:

Kotlin

val imageResponse = model.generateImages(
  prompt = "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest",
)
val image = imageResponse.images.first
val bitmapImage = image.asBitmap()

Java

CompletableFuture<GenerateContentResponse> futureResponse =
    model.generateContent(
        Content.newBuilder()
            .addParts(
                Part.newBuilder()
                    .setText("A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest")
                    .build())
            .build());

try {
  GenerateContentResponse imageResponse = futureResponse.get();
  List<GeneratedImage> images =
      imageResponse
          .getCandidates(0)
          .getContent()
          .getParts(0)
          .getInlineData()
          .getImagesList();

  if (!images.isEmpty()) {
    GeneratedImage image = images.get(0);
    Bitmap bitmapImage = image.asBitmap();
    // Use bitmapImage
  }
} catch (ExecutionException | InterruptedException e) {
  e.printStackTrace();
}

Editar imagens com o Imagen

Os SDKs do Firebase AI Logic oferecem recursos avançados de edição de imagens com o modelo Imagen, permitindo que você:

  • Edite imagens com base em máscaras, o que inclui ações como inserir ou remover objetos, estender o conteúdo da imagem além dos limites originais e mudar planos de fundo.
  • Personalize imagens aplicando estilos específicos (padrões, texturas ou estilos de artistas), focando em vários temas (como produtos, pessoas ou animais) ou seguindo diferentes controles (como um esboço desenhado à mão, uma imagem de borda nítida ou uma malha facial).

Inicialização do modelo

Para usar os recursos de edição do Imagen, especifique um modelo que ofereça suporte à edição de imagens, como imgen-3.0-capability-001. versão do modelo:

val imagenModel = Firebase.ai(backend = GenerativeBackend.vertexAI())
.imagenModel("imagen-3.0-capability-001")

Edição baseada em máscara

Com a edição baseada em máscara do Imagen, é possível fazer modificações nas imagens definindo áreas específicas para o modelo manipular. Com esse recurso, é possível realizar várias ações, como criar e aplicar máscaras, inserir ou remover objetos e expandir o conteúdo da imagem além dos limites originais.

Criar uma máscara

Para fazer edições baseadas em máscara, como inserir ou remover objetos, é necessário definir a área que precisa ser editada pelo modelo, a máscara.

Para criar uma máscara, faça com que o modelo a gere automaticamente usando ImagenBackgroundMask() ou ImagenSemanticMask(), transmitindo um ID de classe.

Também é possível desenhar manualmente a máscara na tela gerando um bitmap de máscara e convertendo-o em um ImagenRawMask. Usando detectDragGestures e Canvas, é possível implementar uma interface do usuário de desenho de máscara com o Jetpack Compose no seu app da seguinte maneira:

import androidx.compose.ui.graphics.Color as ComposeColor
[...]

@Composable
fun ImagenEditingMaskEditor(
    sourceBitmap: Bitmap,
    onMaskFinalized: (Bitmap) -> Unit,
) {

    val paths = remember { mutableStateListOf<Path>() }
    var currentPath by remember { mutableStateOf<Path?>(null) }
    var scale by remember { mutableFloatStateOf(1f) }
    var offsetX by remember { mutableFloatStateOf(0f) }
    var offsetY by remember { mutableFloatStateOf(0f) }

    Column(
        modifier = Modifier.fillMaxSize(),
    ) {
        Box(
            modifier = Modifier
                .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 = null,
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Fit,
            )
            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 = ComposeColor.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)
                    }
                }
            }
        }
        Button(
            onClick = {
                val maskBitmap = createMask(sourceBitmap, paths)
                onMaskFinalized(maskBitmap)
            },
        ) {
            Text("Save mask")
        }
    }
}

Em seguida, crie o bitmap de máscara desenhando os caminhos na tela:

import android.graphics.Color as AndroidColor
import android.graphics.Paint
[...]

private fun createMaskBitmap(
    sourceBitmap: Bitmap,
    paths: SnapshotStateList<Path>,
): Bitmap {
    val maskBitmap = createBitmap(sourceBitmap.width, sourceBitmap.height)
    val canvas = android.graphics.Canvas(maskBitmap)
    val paint = Paint().apply {
        color = AndroidColor.RED
        strokeWidth = 70f
        style = Paint.Style.STROKE
        strokeCap = Paint.Cap.ROUND
        strokeJoin = Paint.Join.ROUND
        isAntiAlias = true
    }
    paths.forEach { path -> canvas.drawPath(path.asAndroidPath(), paint) }

    return maskBitmap
}

Verifique se a máscara tem o mesmo tamanho da imagem de origem. Consulte os exemplos de catálogo da IA do Imagen para mais detalhes.

Inserir objetos

É possível inserir um novo objeto ou conteúdo em uma imagem existente, também chamado de repintura. O modelo vai gerar e inserir o novo conteúdo na área mascarada especificada.

Para isso, use a função editImage(). Você precisa fornecer a imagem original, uma máscara e um comando de texto descrevendo o conteúdo que você quer inserir. Além disso, transmita um objeto ImagenEditingConfig, garantindo que a propriedade editMode esteja definida como ImagenEditMode.INPAINT_INSERTION.

suspend fun insertFlowersIntoImage(
  model: ImagenModel,
  originalImage: Bitmap,
  mask: ImagenMaskReference): ImagenGenerationResponse<ImagenInlineImage> {
    val prompt = "a vase of flowers"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            mask),
        prompt = prompt,
        // Define the editing configuration for inpainting and insertion.
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
    )

    return editedImage
}

Remover objetos

Com o retoque, é possível remover objetos indesejados de uma imagem. Para fazer isso, use a função editImage. Você precisa fornecer a imagem original e uma máscara que destaque o objeto a ser removido. Se quiser, inclua um comando de texto para descrever o objeto, o que pode ajudar o modelo a fazer uma identificação precisa. Além disso, é necessário definir o editMode em ImagenEditingConfig como ImagenEditMode.INPAINT_REMOVAL.

suspend fun removeBallFromImage(model: ImagenModel, originalImage: Bitmap, mask: ImagenMaskReference): ImagenGenerationResponse<ImagenInlineImage> {

    // Optional: provide the prompt describing the content to be removed.
    val prompt = "a ball"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            mask
        ),
        prompt = prompt,
        // Define the editing configuration for inpainting and removal.
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_REMOVAL)
    )

    return editedImage
}

Ampliar conteúdo da imagem

É possível expandir uma imagem além dos limites originais, o que é conhecido como pintura externa, usando a função outpaintImage(). Essa função requer a imagem original e o Dimensions necessário da imagem expandida. Se quiser, inclua um comando descritivo para a expansão e especifique o ImagenImagePlacement da imagem original na nova imagem gerada:

suspend fun expandImage(originalImage: Bitmap, imagenModel: ImagenModel): ImagenGenerationResponse<ImagenInlineImage> {

    // Optionally describe what should appear in the expanded area.
    val prompt = "a sprawling sandy beach next to the ocean"

    val editedImage = model.outpaintImage(
        ImagenRawImage(originalImage),
        Dimension(width, height),
        prompt = prompt,
        newPosition = ImagenImagePlacement.LEFT_CENTER
    )


    return editedImage
}

Substituir o plano de fundo

É possível substituir o plano de fundo de uma imagem preservando o assunto em primeiro plano. Para fazer isso, use a função editImage. Transmita a imagem original, um objeto ImagenBackgroundMask (que contém um comando de texto para o novo plano de fundo) e um ImagenEditingConfig com a propriedade editMode definida como ImagenEditMode.INPAINT_INSERTION.

suspend fun replaceBackground(model: ImagenModel, originalImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {
    // Provide the prompt describing the new background.
    val prompt = "space background"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            ImagenBackgroundMask(),
        ),
        prompt = prompt,
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
    )

    return editedImage
}

Personalização

Use a capacidade de personalização do Imagen para gerar ou editar imagens com base em imagens de referência que especificam um assunto, controle ou estilo. Isso é feito fornecendo um comando de texto com uma ou mais imagens de referência para orientar o modelo.

Personalizar com base em um assunto

Você pode gerar novas imagens de um assunto específico com base em uma imagem de referência (por exemplo, um produto, uma pessoa ou um animal). Basta fornecer um comando de texto e pelo menos uma imagem de referência do assunto. Por exemplo, você pode enviar uma foto do seu animal de estimação e gerar uma nova imagem dele em um ambiente completamente diferente.

Para fazer isso, defina a referência do assunto usando ImagenSubjectReference e transmita para editImage junto com seu comando. Além disso, inclua um ImagenEditingConfig que especifica o número de editSteps. Um valor de editSteps mais alto geralmente leva a resultados de melhor qualidade:

suspend fun customizeCatImage(model: ImagenModel, referenceCatImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {

    // Define the subject reference using the reference image.
    val subjectReference = ImagenSubjectReference(
        image = referenceCatImage,
        referenceID = 1,
        description = "cat",
        subjectType = ImagenSubjectReferenceType.ANIMAL
    )

    // Provide a prompt that describes the final image.
    // The "[1]" links the prompt to the subject reference with ID 1.
    val prompt = "A cat[1] flying through outer space"

    // Use the editImage API to perform the subject customization.
    val editedImage = model.editImage(
        references = listOf(subjectReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50 // Number of editing steps, a higher value can improve quality
        )
    )

    return editedImage
}

Personalizar com base em um controle

Essa técnica gera uma nova imagem com base em uma imagem de referência de controle, como um esboço feito à mão ("rabisco"), uma imagem de borda de Canny ou uma malha facial. O modelo usa a imagem de controle como um guia estrutural para o layout e a composição da nova imagem, enquanto o comando de texto fornece detalhes como cor e textura.

Defina uma referência de controle com ImagenControlReference e forneça-a a editImage junto com um comando e ImagenEditingConfig com o número de editSteps. Um valor mais alto pode melhorar a qualidade:

suspend fun customizeCatImageByControl(model: ImagenModel, referenceCatImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {
 
   // Define the subject reference using the reference image.
    val controlReference = ImagenControlReference(
        image = referenceImage,
        referenceID = 1,
        controlType = CONTROL_TYPE_SCRIBBLE
    )

    val prompt = "A cat flying through outer space arranged like the scribble map[1]"

    val editedImage = model.editImage(
        references = listOf(controlReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50
        )
    )

    return editedImage
}

Personalizar com base em um estilo

Você pode gerar ou editar uma imagem para corresponder a um estilo específico de uma imagem de referência, como padrão, textura ou design. O modelo usa a imagem de referência para entender a estética necessária e a aplica à nova imagem descrita no comando de texto. Por exemplo, você pode gerar uma imagem de um gato no estilo de uma pintura famosa fornecendo uma imagem dessa pintura.

Defina uma referência de estilo com ImagenStyleReference e forneça-a a editImage junto com um comando e ImagenEditingConfig com o número de editSteps. Um valor mais alto pode melhorar a qualidade:

suspend fun customizeImageByStyle(model: ImagenModel, referenceVanGoghImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {

    // Define the style reference using the reference image.
    val styleReference = ImagenStyleReference(
        image = referenceVanGoghImage,
        referenceID = 1,
        description = "Van Gogh style"
    )

    // Provide a prompt that describes the final image.
    // The "1" links the prompt to the style reference with ID 1.
    val prompt = "A cat flying through outer space, in the Van Gogh style[1]"

    // Use the editImage API to perform the style customization.
    val editedImage = model.editImage(
        references = listOf(styleReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50 // Number of editing steps, a higher value can improve quality
        )
    )

    return editedImage 
}

Próximas etapas