1. Visão geral
No primeiro codelab, você vai fazer upload de fotos em um bucket. Isso vai gerar um evento de criação de arquivo que será processado por uma função. A função vai fazer uma chamada à API Vision para analisar imagens e salvar os resultados em um datastore.

O que você vai aprender
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore
2. Configuração e requisitos
Configuração de ambiente autoguiada
- Faça login no Console do Google Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.



- O Nome do projeto é o nome de exibição para os participantes do projeto. É uma string de caracteres não usada pelas APIs do Google É possível atualizar o local a qualquer momento.
- O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser alterado após a definição. O console do Cloud gera automaticamente uma string exclusiva. Em geral, não importa o que seja. Na maioria dos codelabs, é necessário fazer referência ao ID do projeto, normalmente identificado como
PROJECT_ID. Se você não gostar do ID gerado, crie outro aleatório. Se preferir, teste o seu e confira se ele está disponível. Ele não pode ser mudado após essa etapa e permanece durante o projeto. - Para sua informação, há um terceiro valor, um Número do projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
- Em seguida, ative o faturamento no console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não será muito cara, se tiver algum custo. Para encerrar os recursos e evitar cobranças além deste tutorial, exclua os recursos criados ou o projeto inteiro. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.
Inicie o Cloud Shell
Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.
No Console do Google Cloud, clique no ícone do Cloud Shell na barra de ferramentas superior à direita:

O provisionamento e a conexão com o ambiente levarão apenas alguns instantes para serem concluídos: Quando o processamento for concluído, você verá algo como:

Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Neste codelab, todo o trabalho pode ser feito com um navegador. Você não precisa instalar nada.
3. Ativar APIs
Neste laboratório, você vai usar o Cloud Functions e a API Vision, mas primeiro eles precisam ser ativados no console do Cloud ou com gcloud.
Para ativar a API Vision no Console do Cloud, pesquise Cloud Vision API na barra de pesquisa:

Você vai acessar a página da API Cloud Vision:

Clique no botão ENABLE.
Como alternativa, você também pode ativar o Cloud Shell usando a ferramenta de linha de comando gcloud.
No Cloud Shell, execute este comando:
gcloud services enable vision.googleapis.com
A operação será concluída com sucesso:
Operation "operations/acf.12dba18b-106f-4fd2-942d-fea80ecc5c1c" finished successfully.
Ative também o Cloud Functions:
gcloud services enable cloudfunctions.googleapis.com
4. Criar o bucket (console)
Crie um bucket de armazenamento para as fotos. É possível fazer isso no console do Google Cloud Platform ( console.cloud.google.com) ou com a ferramenta de linha de comando gsutil no Cloud Shell ou no seu ambiente de desenvolvimento local.
Acesse "Storage"
No menu "hambúrguer" (☰), navegue até a página Storage.

Nomeie seu bucket
Clique no botão CREATE BUCKET.

Clique em CONTINUE.
Escolher local

Crie um bucket multirregional na região de sua escolha (aqui Europe).
Clique em CONTINUE.
Escolher a classe de armazenamento padrão

Escolha a classe de armazenamento Standard para seus dados.
Clique em CONTINUE.
Definir controle de acesso

Como você vai trabalhar com imagens acessíveis ao público, é importante que todas as fotos armazenadas nesse bucket tenham o mesmo controle de acesso uniforme.
Escolha a opção de controle de acesso Uniform.
Clique em CONTINUE.
Definir proteção/criptografia

Mantenha o padrão (Google-managed key)), já que você não vai usar suas próprias chaves de criptografia.
Clique em CREATE para finalizar a criação do bucket.
Adicionar allUsers como leitor de armazenamento
Acesse a guia Permissions:

Adicione um membro allUsers ao bucket com o papel Storage > Storage Object Viewer da seguinte maneira:

Clique em SAVE.
5. Criar o bucket (gsutil)
Também é possível usar a ferramenta de linha de comando gsutil no Cloud Shell para criar buckets.
No Cloud Shell, defina uma variável para o nome exclusivo do bucket. O Cloud Shell já tem GOOGLE_CLOUD_PROJECT definido como o ID exclusivo do seu projeto. Você pode anexar isso ao nome do bucket.
Exemplo:
export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Crie uma zona padrão multirregional na Europa:
gsutil mb -l EU gs://${BUCKET_PICTURES}
Verifique se o acesso uniforme no nível do bucket está ativado:
gsutil uniformbucketlevelaccess set on gs://${BUCKET_PICTURES}
Torne o bucket público:
gsutil iam ch allUsers:objectViewer gs://${BUCKET_PICTURES}
Se você acessar a seção Cloud Storage do console, vai encontrar um bucket uploaded-pictures público:

Teste se é possível fazer upload de fotos para o bucket e se elas estão disponíveis publicamente, conforme explicado na etapa anterior.
6. Testar o acesso público ao bucket
Voltando ao navegador de armazenamento, você vai ver seu bucket na lista, com acesso "Público" (incluindo um sinal de alerta lembrando que qualquer pessoa tem acesso ao conteúdo desse bucket).

Seu bucket está pronto para receber fotos.
Se você clicar no nome do bucket, vai ver os detalhes dele.

Lá, você pode clicar no botão Upload files para testar se é possível adicionar uma imagem ao bucket. Uma janela pop-up vai pedir para você selecionar um arquivo. Depois de selecionado, ele será enviado por upload para seu bucket, e você verá novamente o acesso public atribuído automaticamente a esse novo arquivo.

Ao lado do marcador de acesso Public, você também vai encontrar um pequeno ícone de link. Ao clicar nele, seu navegador vai navegar até o URL público da imagem, que terá o seguinte formato:
https://storage.googleapis.com/BUCKET_NAME/PICTURE_FILE.png
Em que BUCKET_NAME é o nome globalmente exclusivo escolhido para o bucket e o nome do arquivo da imagem.
Ao clicar na caixa de seleção ao lado do nome da imagem, o botão DELETE será ativado, e você poderá excluir a primeira imagem.
7. Criar a função
Nesta etapa, você vai criar uma função que reage a eventos de upload de imagens.
Acesse a seção Cloud Functions do console do Google Cloud. Ao acessar, o serviço do Cloud Functions será ativado automaticamente.

Clique em Create function.
Escolha um nome (por exemplo, picture-uploaded) e a região (lembre-se de ser consistente com a escolha da região para o bucket):

Há dois tipos de funções:
- Funções HTTP que podem ser invocadas por um URL (ou seja, uma API da Web).
- Funções em segundo plano que podem ser acionadas por algum evento.
Você quer criar uma função em segundo plano que seja acionada quando um novo arquivo for enviado para o bucket Cloud Storage:

Você tem interesse no tipo de evento Finalize/Create, que é acionado quando um arquivo é criado ou atualizado no bucket:

Selecione o bucket criado anteriormente para informar ao Cloud Functions que ele precisa receber uma notificação quando um arquivo for criado ou atualizado nesse bucket específico:

Clique em Select para escolher o bucket que você criou antes e em Save.

Antes de clicar em "Próxima", expanda e modifique os padrões (256 MB de memória) em Configurações de ambiente de execução, build, conexões e segurança e atualize para 1 GB.

Depois de clicar em Next, você pode ajustar o ambiente de execução, o código-fonte e o ponto de entrada.
Mantenha o Inline editor para essa função:

Selecione um dos ambientes de execução do Java, por exemplo, Java 11:

O código-fonte consiste em um arquivo Java e um arquivo pom.xml do Maven que fornece vários metadados e dependências.
Deixe o snippet de código padrão: ele registra o nome do arquivo da imagem enviada:

Por enquanto, mantenha o nome da função a ser executada como Example para fins de teste.
Clique em Deploy para criar e implantar a função. Quando a implantação for concluída, uma marca de seleção verde vai aparecer na lista de funções:

8. Testar a função
Nesta etapa, teste se a função responde a eventos de armazenamento.
No menu "hambúrguer" (☰), volte para a página Storage.
Clique no bucket de imagens e em Upload files para fazer upload de uma imagem.

Navegue novamente no console do Cloud para acessar a página Logging > Logs Explorer.
No seletor Log Fields, selecione Cloud Function para ver os registros dedicados às suas funções. Role a tela para baixo pelos campos de registro e selecione uma função específica para ter uma visão mais detalhada dos registros relacionados a ela. Selecione a função picture-uploaded.
Você vai ver os itens de registro mencionando a criação da função, os horários de início e término dela e nossa instrução de registro real:

Nossa instrução de registro lê: Processing file: pic-a-daily-architecture-events.png, o que significa que o evento relacionado à criação e ao armazenamento dessa imagem foi acionado conforme o esperado.
9. Preparar o banco de dados
Você vai armazenar informações sobre a imagem fornecida pela API Vision no banco de dados Cloud Firestore, um banco de dados de documentos NoSQL rápido, totalmente gerenciado, sem servidor e nativo da nuvem. Prepare seu banco de dados acessando a seção Firestore do Console do Cloud:

Há duas opções: Native mode ou Datastore mode. Use o modo nativo, que oferece recursos extras, como suporte off-line e sincronização em tempo real.
Clique em SELECT NATIVE MODE.

Escolha uma multirregião (aqui na Europa, mas o ideal é pelo menos a mesma região da função e do bucket de armazenamento).
Clique no botão CREATE DATABASE.
Depois que o banco de dados for criado, você verá o seguinte:

Crie uma coleção clicando no botão + START COLLECTION.
Nomeie a coleção pictures.

Não é necessário criar um documento. Você vai adicioná-los de forma programática à medida que novas imagens forem armazenadas no Cloud Storage e analisadas pela API Vision.
Clique em Save.
O Firestore cria um primeiro documento padrão na coleção recém-criada. Você pode excluir esse documento com segurança, porque ele não contém informações úteis:

Os documentos que serão criados de forma programática na nossa coleção vão conter quatro campos:
- name (string): o nome do arquivo da imagem enviada, que também é a chave do documento.
- labels (matriz de strings): os rótulos dos itens reconhecidos pela API Vision.
- color (string): o código hexadecimal da cor dominante (por exemplo, #ab12ef)
- created (data): o carimbo de data/hora de quando os metadados da imagem foram armazenados.
- thumbnail (booleano): um campo opcional que estará presente e será verdadeiro se uma imagem em miniatura tiver sido gerada para essa foto.
Como vamos pesquisar no Firestore para encontrar fotos com miniaturas disponíveis e classificar pela data de criação, precisamos criar um índice de pesquisa.
É possível criar o índice com o seguinte comando no Cloud Shell:
gcloud firestore indexes composite create \
--collection-group=pictures \
--field-config field-path=thumbnail,order=descending \
--field-config field-path=created,order=descending
Ou você pode fazer isso no console do Cloud. Clique em Indexes na coluna de navegação à esquerda e crie um índice composto, conforme mostrado abaixo:

Clique em Create. A criação do índice pode levar alguns minutos.
10. Atualizar a função
Volte para a página Functions para atualizar a função e invocar a API Vision para analisar nossas fotos e armazenar os metadados no Firestore.
No menu "hambúrguer" (☰), navegue até a seção Cloud Functions, clique no nome da função, selecione a guia Source e clique no botão EDIT.
Primeiro, edite o arquivo pom.xml, que lista as dependências da função Java. Atualize o código para adicionar a dependência do Maven da API Cloud Vision:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cloudfunctions</groupId>
<artifactId>gcs-function</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-firestore</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vision</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
</dependency>
</dependencies>
<!-- Required for Java 11 functions in the inline editor -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<excludes>
<exclude>.google/</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Agora que as dependências estão atualizadas, vamos trabalhar no código da função atualizando o arquivo Example.java com nosso código personalizado.
Mova o mouse sobre o arquivo Example.java e clique no lápis. Substitua o nome do pacote e o nome do arquivo por src/main/java/fn/ImageAnalysis.java.
Substitua o código em ImageAnalysis.java pelo código abaixo. Isso será explicado na próxima etapa.
package fn;
import com.google.cloud.functions.*;
import com.google.cloud.vision.v1.*;
import com.google.cloud.vision.v1.Feature.Type;
import com.google.cloud.firestore.*;
import com.google.api.core.ApiFuture;
import java.io.*;
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import java.util.logging.Logger;
import fn.ImageAnalysis.GCSEvent;
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
private static final Logger logger = Logger.getLogger(ImageAnalysis.class.getName());
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException, ExecutionException {
String fileName = event.name;
String bucketName = event.bucket;
logger.info("New picture uploaded " + fileName);
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
List<AnnotateImageRequest> requests = new ArrayList<>();
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
requests.add(request);
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result = vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
if (responses.size() == 0) {
logger.info("No response received from Vision API.");
return;
}
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn = imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch = response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
// Saving result to Firestore
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
}
}
private static String rgbHex(float red, float green, float blue) {
return String.format("#%02x%02x%02x", (int)red, (int)green, (int)blue);
}
public static class GCSEvent {
String bucket;
String name;
}
}

11. Conheça a função
Vamos analisar as várias partes interessantes.
Primeiro, vamos incluir as dependências específicas no arquivo pom.xml do Maven. As bibliotecas de cliente Java do Google publicam um Bill-of-Materials(BOM) para eliminar conflitos de dependência. Ao usar a BoM, não é necessário especificar nenhuma versão para as bibliotecas de cliente individuais do Google.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Em seguida, preparamos um cliente para a API Vision:
...
try (ImageAnnotatorClient vision = ImageAnnotatorClient.create()) {
...
Agora vem a estrutura da nossa função. Capturamos do evento de entrada os campos de interesse e os mapeamos para a estrutura GCSEvent que definimos:
...
public class ImageAnalysis implements BackgroundFunction<GCSEvent> {
@Override
public void accept(GCSEvent event, Context context)
throws IOException, InterruptedException,
ExecutionException {
...
public static class GCSEvent {
String bucket;
String name;
}
Observe a assinatura, mas também como recuperamos o nome do arquivo e do bucket que acionaram a função do Cloud.
Para referência, veja como é o payload do evento:
{
"bucket":"uploaded-pictures",
"contentType":"image/png",
"crc32c":"efhgyA==",
"etag":"CKqB956MmucCEAE=",
"generation":"1579795336773802",
"id":"uploaded-pictures/Screenshot.png/1579795336773802",
"kind":"storage#object",
"md5Hash":"PN8Hukfrt6C7IyhZ8d3gfQ==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/uploaded-pictures/o/Screenshot.png?generation=1579795336773802&alt=media",
"metageneration":"1",
"name":"Screenshot.png",
"selfLink":"https://www.googleapis.com/storage/v1/b/uploaded-pictures/o/Screenshot.png",
"size":"173557",
"storageClass":"STANDARD",
"timeCreated":"2020-01-23T16:02:16.773Z",
"timeStorageClassUpdated":"2020-01-23T16:02:16.773Z",
"updated":"2020-01-23T16:02:16.773Z"
}
Preparamos uma solicitação para enviar pelo cliente Vision:
ImageSource imageSource = ImageSource.newBuilder()
.setGcsImageUri("gs://" + bucketName + "/" + fileName)
.build();
Image image = Image.newBuilder()
.setSource(imageSource)
.build();
Feature featureLabel = Feature.newBuilder()
.setType(Type.LABEL_DETECTION)
.build();
Feature featureImageProps = Feature.newBuilder()
.setType(Type.IMAGE_PROPERTIES)
.build();
Feature featureSafeSearch = Feature.newBuilder()
.setType(Type.SAFE_SEARCH_DETECTION)
.build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
.addFeatures(featureLabel)
.addFeatures(featureImageProps)
.addFeatures(featureSafeSearch)
.setImage(image)
.build();
Estamos pedindo três recursos principais da API Vision:
- Detecção de rótulos: para entender o que há nas fotos
- Propriedades da imagem: para fornecer atributos interessantes da imagem (a cor dominante)
- Pesquisa segura: para saber se a imagem é segura para exibição (não deve conter conteúdo adulto, médico, sugestivo ou violento)
Neste ponto, podemos fazer a chamada para a API Vision:
...
logger.info("Calling the Vision API...");
BatchAnnotateImagesResponse result =
vision.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = result.getResponsesList();
...
Para referência, confira como é a resposta da API Vision:
{
"faceAnnotations": [],
"landmarkAnnotations": [],
"logoAnnotations": [],
"labelAnnotations": [
{
"locations": [],
"properties": [],
"mid": "/m/01yrx",
"locale": "",
"description": "Cat",
"score": 0.9959855675697327,
"confidence": 0,
"topicality": 0.9959855675697327,
"boundingPoly": null
},
✄ - - - ✄
],
"textAnnotations": [],
"localizedObjectAnnotations": [],
"safeSearchAnnotation": {
"adult": "VERY_UNLIKELY",
"spoof": "UNLIKELY",
"medical": "VERY_UNLIKELY",
"violence": "VERY_UNLIKELY",
"racy": "VERY_UNLIKELY",
"adultConfidence": 0,
"spoofConfidence": 0,
"medicalConfidence": 0,
"violenceConfidence": 0,
"racyConfidence": 0,
"nsfwConfidence": 0
},
"imagePropertiesAnnotation": {
"dominantColors": {
"colors": [
{
"color": {
"red": 203,
"green": 201,
"blue": 201,
"alpha": null
},
"score": 0.4175916016101837,
"pixelFraction": 0.44456374645233154
},
✄ - - - ✄
]
}
},
"error": null,
"cropHintsAnnotation": {
"cropHints": [
{
"boundingPoly": {
"vertices": [
{ "x": 0, "y": 118 },
{ "x": 1177, "y": 118 },
{ "x": 1177, "y": 783 },
{ "x": 0, "y": 783 }
],
"normalizedVertices": []
},
"confidence": 0.41695669293403625,
"importanceFraction": 1
}
]
},
"fullTextAnnotation": null,
"webDetection": null,
"productSearchResults": null,
"context": null
}
Se não houver um erro retornado, podemos continuar. Por isso, temos este bloco "if":
AnnotateImageResponse response = responses.get(0);
if (response.hasError()) {
logger.info("Error: " + response.getError().getMessage());
return;
}
Vamos receber os rótulos das coisas, categorias ou temas reconhecidos na imagem:
List<String> labels = response.getLabelAnnotationsList().stream()
.map(annotation -> annotation.getDescription())
.collect(Collectors.toList());
logger.info("Annotations found:");
for (String label: labels) {
logger.info("- " + label);
}
Queremos saber a cor dominante da imagem:
String mainColor = "#FFFFFF";
ImageProperties imgProps = response.getImagePropertiesAnnotation();
if (imgProps.hasDominantColors()) {
DominantColorsAnnotation colorsAnn =
imgProps.getDominantColors();
ColorInfo colorInfo = colorsAnn.getColors(0);
mainColor = rgbHex(
colorInfo.getColor().getRed(),
colorInfo.getColor().getGreen(),
colorInfo.getColor().getBlue());
logger.info("Color: " + mainColor);
}
Também estamos usando uma função utilitária para transformar os valores vermelho / verde / azul em um código de cor hexadecimal que pode ser usado em folhas de estilo CSS.
Vamos verificar se a imagem é segura para mostrar:
boolean isSafe = false;
if (response.hasSafeSearchAnnotation()) {
SafeSearchAnnotation safeSearch =
response.getSafeSearchAnnotation();
isSafe = Stream.of(
safeSearch.getAdult(), safeSearch.getMedical(), safeSearch.getRacy(),
safeSearch.getSpoof(), safeSearch.getViolence())
.allMatch( likelihood ->
likelihood != Likelihood.LIKELY && likelihood != Likelihood.VERY_LIKELY
);
logger.info("Safe? " + isSafe);
}
Estamos verificando se os atributos adulto, farsa, médico, violência e conteúdo sugestivo não são prováveis ou muito prováveis.
Se o resultado da pesquisa segura for positivo, podemos armazenar metadados no Firestore:
if (isSafe) {
FirestoreOptions firestoreOptions = FirestoreOptions.getDefaultInstance();
Firestore pictureStore = firestoreOptions.getService();
DocumentReference doc = pictureStore.collection("pictures").document(fileName);
Map<String, Object> data = new HashMap<>();
data.put("labels", labels);
data.put("color", mainColor);
data.put("created", new Date());
ApiFuture<WriteResult> writeResult = doc.set(data, SetOptions.merge());
logger.info("Picture metadata saved in Firestore at " + writeResult.get().getUpdateTime());
}
12. Implantar a função
É hora de implantar a função.

Clique no botão DEPLOY para implantar a nova versão e acompanhar o progresso:

13. Teste a função novamente
Depois que a função for implantada, poste uma foto no Cloud Storage, verifique se a função foi invocada, o que a API Vision retorna e se os metadados são armazenados no Firestore.
Volte para Cloud Storage e clique no bucket que criamos no início do laboratório:

Na página de detalhes do bucket, clique no botão Upload files para fazer upload de uma imagem.

No menu "hambúrguer" (☰), navegue até o Logging > Logs Explorer.
No seletor Log Fields, selecione Cloud Function para ver os registros dedicados às suas funções. Role a tela para baixo pelos campos de registro e selecione uma função específica para ter uma visão mais detalhada dos registros relacionados a ela. Selecione a função picture-uploaded.

E, de fato, na lista de registros, posso ver que nossa função foi invocada:

Os registros indicam o início e o fim da execução da função. Entre eles, podemos ver os registros que colocamos na função com as instruções console.log(). Podemos notar que:
- Os detalhes do evento que aciona nossa função,
- Os resultados brutos da chamada da API Vision,
- Os rótulos encontrados na imagem que enviamos,
- As informações de cores dominantes,
- Se a imagem é segura para mostrar,
- E, por fim, esses metadados sobre a imagem foram armazenados no Firestore.

No menu "hambúrguer" (☰), acesse a seção Firestore. Na subseção Data (mostrada por padrão), você vai encontrar a coleção pictures com um novo documento adicionado, correspondente à imagem que você acabou de enviar:

14. Limpeza (opcional)
Se você não pretende continuar com os outros laboratórios da série, limpe os recursos para economizar custos e ser um bom usuário da nuvem. É possível limpar os recursos individualmente da seguinte maneira.
Excluir o bucket:
gsutil rb gs://${BUCKET_PICTURES}
Exclua a função:
gcloud functions delete picture-uploaded --region europe-west1 -q
Exclua a coleção do Firestore selecionando "Excluir coleção" na coleção:

Se preferir, exclua todo o projeto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
15. Parabéns!
Parabéns! Você implementou o primeiro serviço de chaves do projeto!
O que vimos
- Cloud Storage
- Cloud Functions
- API Cloud Vision
- Cloud Firestore