Generative AI – Cosplay Camera App
In this tutorial, we will create an app that uses the Google Gemini Nano Banana to generate a full-body cosplay image from a captured face photo.
In August 2025, Google released Nano Banana (Gemini 2.5 Flash Image), an AI that lets you edit and transform photos with natural language while preserving the features of people and objects. As a fun application, we tried turning face photos into cosplay.
Here is the information of Nano Banana.
https://gemini.google/overview/image-generation
Since Nano Banana cannot be used for free via the API, you’ll need to add a billing method. You can do this in Google AI Studio.
For calling the Google Gemini API, we’ll use Google Apps Script (GAS). The GAS script is written with the help of Google Gemini 2.5 Pro.
What You Will Learn in This Tutorial
- How to use Nano Banana
- How to create an API with GAS
- How to write a GAS script using Gemini
- How to integrate with the Gemini API
Components Used
Button, TextBox, Canvas, Image, HorizontalArrangement, VerticalArrangement, Web, Camera, Sharing, ImageBase641
Blocks Used
Global Variable, if then else, Dictionaries
You can download the source code (aia file) from the download section at the bottom of the page.
Setting up Google Apps Script
See the Generative AI – Doodle Coach App for instructions on obtaining an API key and deploying Google Apps Script.
Code.gs
function doPost(e) {
// Main handler for POST requests from App Inventor.
// Returns a JSON string with: { status, message, prompt, imageData }
let response = {
status: 'error',
message: 'An unknown error occurred.',
imageData: null
};
try {
// Validate and parse incoming JSON payload
if (!e || !e.postData || !e.postData.contents) {
throw new Error('Invalid request: No data received.');
}
const jsonData = JSON.parse(e.postData.contents);
// Expecting base64 image data and optional character/prompt fields
const base64Data = jsonData.imageData;
const character = jsonData.character;
const prompt =
jsonData.prompt ||
`Convert this into a full-body photo, do not modify the face at all, and apply cosplay as ${character}. No textual reply is needed.`;
// Basic input validation
if (!base64Data || typeof base64Data !== 'string') {
throw new Error('Invalid request: imageData is missing or not a string.');
}
// Remove data URL header (e.g., "data:image/png;base64,")
const cleanedBase64 = cleanBase64String(base64Data);
// Read API key from Script Properties
const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
if (!apiKey) {
throw new Error('Configuration error: API key is not set in Script Properties.');
}
// Call Gemini image-generation endpoint
const generatedBase64 = callGeminiApi(apiKey, cleanedBase64, prompt);
// Success response
response = {
status: 'success',
message: 'Image generated successfully.',
prompt: prompt,
imageData: generatedBase64
};
} catch (error) {
// Failure response
console.error(error.toString());
response.message = error.message;
}
// Return JSON output
return ContentService.createTextOutput(JSON.stringify(response))
.setMimeType(ContentService.MimeType.JSON);
}
function cleanBase64String(base64String) {
// Strip a leading data URL header like "data:image/png;base64,"
return base64String.replace(/^data:image\/[a-z]+;base64,/, '');
}
function callGeminiApi(apiKey, base64Image, prompt) {
// Invoke the Gemini image-generation model with an image + instruction text
const model = 'gemini-2.5-flash-image-preview';
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
const payload = {
contents: [{
parts: [
{ text: prompt },
{
inlineData: {
mimeType: 'image/png', // App Inventor Canvas exports PNG
data: base64Image
}
}
]
}],
generationConfig: {
// Request both image and text modalities in the response
responseModalities: ['IMAGE', 'TEXT']
}
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true // Capture non-2xx responses instead of throwing
};
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
// Find the image part in the response
const imagePart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.inlineData);
if (imagePart && imagePart.inlineData.data) {
// Return base64 image data
return imagePart.inlineData.data;
} else {
// If no image, surface any returned text as the error message
console.error('API response did not contain image data. Response: ' + responseBody);
const textPart = jsonResponse?.candidates?.[0]?.content?.parts?.find(p => p.text);
const errorMessage = textPart ? textPart.text : 'No image data in response.';
throw new Error(`API Error: ${errorMessage}`);
}
} else {
// Non-2xx HTTP response
console.error('API Error. Code: ' + responseCode + '. Body: ' + responseBody);
throw new Error(`API request failed with status code ${responseCode}. Details: ${responseBody}`);
}
}
App Inventor App
From the [Projects] menu, select [Start new project] and name it “CosplayCamera“.